单元测试
Unit Testing 又称模块测试,是针对程序模块(软件设计最新单位,一般为一个函数)进行正确性检验的测试工作。 编写单元测试是提升代码质量和可维护性的重要手段
3A 设计原则
单元测试设计应遵守 3A(Arrange - Action - Assertion)原则,即一个测试用例需要包含安排、行动、断言三部分,并且这三部分的顺序是固定的,不能存在交叉的部分,每个部分应该有唯一的意图。
- Arrange 安排: 测试对象或数据的初始化
- Action 行动: 对初始化对象或数据进行操作
- Assertion 断言: 对结果进行断言
优秀的单元测试应该:
- 简单:将大模块拆分成小模块
- 可读:单一职责原则、通过 3A 原则组织代码
- 可靠:稳定、只有被测试代码存在问题才会失败
- 快速:保证每一个用例的速度
- 独立:无执行顺序依赖、无环境依赖,可并行执行快速启动
编写建议:
- 3A 原则
- 使用嵌套对用例进行分组排版,便于管理、理解和结果展示
- 保证用例的简单,不要在用例中使用判断和循环,否则很难保证用例没有 bug
TODO: TDD VS BDD
| 对比 | TDD(Test-Driven Development) | BDD(Behavior-Driven Development) |
|---|---|---|
| 描述 | 测试驱动开发 | 行为驱动开发 |
| 思想 | 在编写需求功能代码前,先编写单元测试代码,再编写功能代码,满足这些之前编写的单元测试代码 | |
| 面向人员 | 开发人员编写测试代码 | |
| 开发流程 | 1. 编写测试用例 2. 运行测试用例,全部不通过 3. 编写代码,使测试用例通过 4. 优化代码 5. 完成开发重复以上步骤 |
TODO:编写可测的代码
bad 场景一:重复使用实例变量
操作对象内部变量,会产生副租用,多个方法都对对象内部变量进行操作,对象方法的执行顺序可能导致不同的结果,测试很难覆盖全部场景。 修改方案:
- 通过静态方法去状态化
- 通过函数式方法去状态化
bad 场景二:代码结果不可预测
函数内部存在不确定变量(系统时间、随机数等) 解决方案,依赖注入
bad 场景二:代码副作用
单元测试简单实现
function add(x, y) {
return x + y;
}
function minus(x, y) {
return x - y;
}
function multi(x, y) {
return x + y;
}
function expect(value) {
return {
toBe: (actual) => {
if (actual !== value) {
throw new Error(
`预期值与实际值不相等:预期值为${actual},实际值为${value}`
);
}
},
};
}
function test(desc, fun) {
try {
fun();
console.log(`${desc} 通过测试`);
} catch (e) {
console.log(`${desc} 未通过测试(${e})`);
}
}
test("测试加法: add(3, 7)", () => {
expect(add(3, 7)).toBe(10);
});
test("测试减法: minus(3, 3)", () => {
expect(minus(3, 3)).toBe(0);
});
test("测试乘法: minus(3, 3)", () => {
expect(multi(3, 3)).toBe(9);
});