关于单元测试的共识
- 先开发再补单元测试?
行不通,因为代码在编写时并没有考虑可测试性。如果在开发时编写单元测试,会反过来影响代码的设计,使你的设计更容易被测试。而容易被测试的代码一般也是更好维护的。
其次,开发上线后不可能再安排时间回来写单元测试了。
- 稳定的功能才能做单元测试?
稳定和自动化测试本身就是相互矛盾的。如果一个功能非常稳定永远不用修改,那就没有编写单元测试的必要了。修改一个功能10%的代码,我们需要保证另外90%的代码稳定,即使每隔几个月就要修改10%的代码(可以称之为需求频繁改动),写单元测试依然有必要。
如果90%的代码需要频繁改动,那确实没有写单元测试的必要。所以主要还是看代码的寿命。
与集成测试不同,是否做单元测试并不需要以功能为单位来做决策。 通常一个功能在设计上会抽象出很多小的模块或函数,可以对其中相对稳定的部分做单元测试。即使是频繁变更的功能,大概率也能找到相对稳定的模块。如果找不到,那很有可能是代码设计上出了问题。
编写易于测试的代码
单一职责
编程基本意识,略。
抽离外部依赖
确定测试单元:在测试单元内不应包含外部依赖,应该将依赖作为参数。测试则采用mock数据
分辨需要提取的外部依赖:不稳定的外部依赖模块。如接口、CDN资源等,这类依赖缺少版本控制,存在不确定性。而指定版本的外部依赖则可以认为是稳定的,如npm包。
抽离存在副作用的函数
副作用”是指除函数返回值之外的任何变更,包括 state 的更改或者其他行为。一些常见的副作用是:
- 在控制台打印日志
- 保存文件
- 设置异步定时器
- 发送 AJAX HTTP 请求
- 修改存在于函数之外的某些 state,或改变函数的参数
- 生成随机数或唯一随机 ID(例如
Math.random()或Date.now())
React组件设计原则
要对一个React组件做单元测试时,一开始可能会觉得无从下手。我们先将React组件抽象为如下数据流:
测试什么?
- Action --> state:动作触发之后,组件中所有state的状态是否正确
- Props --> view:对于pure component,props与view之间的关系是否一一对应。
- Function:流程图中涉及到的函数
抽象state
使用react自带的reducer或redux将state组织起来,便于测试Action --> state是否正确
相关文档:
使用redux:
使用 React reducer:beta.reactjs.org/reference/r…
减少状态
推荐: state(A) --> fun(A) --> B --> view
不推荐:state(A) --> fun(A) --> state(B) --> view
UI&交互组件 与 业务分开
用redux可以实现这一点。这种方式是将逻辑放在redux里面了
提升通用性
案例:
重构前
export const getNavList = (id: number, departmentList: IDepartment[]): IDepartment[] => {
const newList: IDepartment[] = [];
let parent = departmentList.find((department) => id === department.id);
while (parent) {
newList.unshift(parent);
parent = departmentList.find((department) => department.id === parent!.parentId);
}
return newList;
};
重构后
export const getParents = <T extends { id: unknown; parentId: unknown }>(
id: unknown,
totalData: T[],
): T[] => {
const newList: T[] = [];
let parent = totalData.find((department) => id === department.id);
while (parent) {
newList.unshift(parent);
parent = totalData.find((department) => department.id === parent!.parentId);
}
return newList;
};
心得
- 最大的难度和价值不在于编写单元测试本身,而在于编写易于测试的代码