可测试的纯函数与高阶函数需满足输入决定输出、无副作用、显式依赖;纯函数禁止读写外部状态,高阶函数支持逻辑插拔与隔离,测试聚焦输入输出及函数调用行为。

编写可测试的纯函数与高阶函数,关键在于让函数行为完全由输入决定、不依赖外部状态、也不改变外部环境。这样测试时只需关注“给什么,得什么”,无需模拟或清理副作用。
纯函数:只靠参数,不碰外界
纯函数必须满足两个硬性条件:相同输入永远返回相同输出;执行过程不读写全局变量、不修改入参、不发起请求、不打印日志。一旦违反,就失去可预测性,测试成本陡增。
- 把外部依赖(如税率、配置、时间)显式传入参数,而不是从闭包或模块顶层读取。例如:
calculateTax(amount, rate)比calculateTax(amount)(内部读取taxRate)更易测、更可靠。 - 避免修改传入的对象或数组。需要变更时,用展开语法、
map、structuredClone等创建新副本。比如处理用户列表,不要users.push(newItem),而用[...users, newItem]。 - 对本该随机或异步的行为做“参数化抽象”。例如把
Math.random()替换为接收一个randomFn: () => number参数,测试时传入固定返回值的函数即可断言结果。
高阶函数:让逻辑可插拔、可隔离
高阶函数本身不是“可测试”的终点,而是让纯函数能被组合、复用、替换的桥梁。它让测试焦点从“怎么运行”转向“传了什么逻辑、是否正确调用”。
- 当函数接收另一个函数作为参数(如
map(fn, arr)),测试重点是:是否对每个元素调用fn,是否按顺序收集结果,是否未修改原数组。不必测试fn本身——那是它自己的单元测试职责。 - 当函数返回一个函数(如柯里化
add(2)返回(b) => 2 + b),测试应验证返回的函数是否封装了预期的闭包值,并能独立正确执行。 - 避免在高阶函数内部引入副作用。例如
withLogging(fn)不该直接console.log,而应返回一个新函数,把日志逻辑也变成可选、可替换的纯行为。
测试策略:轻模拟、重断言
纯函数 + 高阶函数的组合,天然适配“输入-输出”驱动的测试方式。不需要复杂 mocking,多数场景靠构造输入、断言输出就能覆盖主路径。
- 对纯函数,每个测试用例明确写清输入和期望输出。例如
roundFix2(0.0, 3.14159)应返回{ accum: 0.14159..., nRounded: 3 }—— 状态更新也是输出的一部分。 - 对高阶函数,用简单存根(stub)代替真实回调。比如测试
filter(predicate, list),传入(x) => x > 5这样的内联函数,或提前定义好行为确定的 predicate,观察过滤结果是否匹配。 - 边界情况单独覆盖:空数组、
null/undefined输入、极值、类型错误等。纯函数没有隐藏分支,这些 case 全部可枚举、可验证。
实际落地小提醒
不必一步到位重写整个项目。从工具函数开始:格式化日期、计算折扣、解析 URL 参数……这些天然适合纯函数。再逐步把循环逻辑替换成 map/filter/reduce,自然带入高阶函数思维。每次重构后,你会发现测试代码变短了,出问题时定位更快了,多人协作时也更少因“谁改了全局状态”而扯皮。











