基于属性的 TypeScript 测试:如何捕获你未曾寻找的 Bug

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

引言:为什么单元测试无法解决所有问题

\n\n

想象一下,您编写了一个数组排序函数。您用十几个单元测试覆盖了它:空数组、单个元素、已排序列表、逆序排列。测试全部通过。您对代码充满信心。但一周后,生产环境崩溃了——问题出在一个包含 1000 个元素、有重复项和负数的数组上。熟悉吗?

\n\n

传统的基于示例的 TypeScript 测试 只检查您想到的场景。但如果 Bug 隐藏在您未曾想到的组合中呢?这时,基于属性的测试 登场了——这种方法会自动生成数千个随机输入数据,并检查您代码的不变量。

\n\n

在本文中,我们将探讨如何使用 fast-check 库在 TypeScript 中进行基于属性的测试,应该检查哪些属性,以及这如何改变开发方法。

\n\n

什么是基于属性的测试?

\n\n

与其编写:

\n
it('排序 [3, 1, 2] → [1, 2, 3]', () => {\n  expect(sort([3, 1, 2])).toEqual([1, 2, 3]);\n});\n
\n\n

不如描述一个属性——一个对于所有有效输入数据都必须为真的断言:

\n
it('排序后的数组不改变元素顺序', () => {\n  fc.assert(\n    fc.property(fc.array(fc.integer()), (arr) => {\n      const sorted = sort(arr);\n      // 属性 1:长度保持不变\n      expect(sorted.length).toBe(arr.length);\n      // 属性 2:原始数组的每个元素都存在\n      // 属性 3:数组按升序排序\n      for (let i = 1; i < sorted.length; i++) {\n        expect(sorted[i - 1]).toBeLessThanOrEqual(sorted[i]);\n      }\n    })\n  );\n});\n
\n\n

fast-check 库会自动生成数百个随机数组(包括空数组、包含 NaN、小数、极大值的数组),并在每个数组上运行您的测试。只要有一个测试失败,您就会发现问题所在。

\n\n

为什么在 TypeScript 中特别需要它?

\n\n

TypeScript 提供了静态类型检查,但它不能保证逻辑的正确性。基于属性的 TypeScript 测试 弥补了“类型匹配”和“代码正确运行”之间的差距。它特别适用于:

\n\n
    \n
  • 数据转换函数(map、filter、reduce)
  • \n
  • 解析器和验证器
  • \n
  • 具有复杂逻辑的算法
  • \n
  • 处理边界值的函数
  • \n
\n\n

fast-check 库:快速入门

\n\n

在项目中安装 fast-check:

\n
npm install --save-dev fast-check\n
\n\n

基本语法:

\n
import fc from 'fast-check';\n\ndescribe('reverse 函数的属性', () => {\n  it('reverse(reverse(arr)) === arr', () => {\n    fc.assert(\n      fc.property(fc.array(fc.anything()), (arr) => {\n        expect(reverse(reverse(arr))).toEqual(arr);\n      })\n    );\n  });\n});\n
\n\n

这里 fc.array(fc.anything()) 是一个任意数组的生成器。您可以组合使用生成器:

\n
    \n
  • fc.integer() — 整数
  • \n
  • fc.string() — 字符串
  • \n
  • fc.record({ name: fc.string(), age: fc.integer({ min: 0, max: 120 }) }) — 对象
  • \n
  • fc.oneof(...) — 从多个生成器中随机选择
  • \n
\n\n

应该检查哪些属性?5 个实用示例

\n\n

1. 不变性(可逆性)

\n

任何具有逆运算的操作都应该是可逆的:

\n
it('JSON 序列化和反序列化', () => {\n  fc.assert(\n    fc.property(fc.object(), (obj) => {\n      const json = JSON.stringify(obj);\n      const parsed = JSON.parse(json);\n      expect(parsed).toEqual(obj);\n    })\n  );\n});\n<                

博客

书籍推荐