- 单元测试特指被测试对象为程序中最小组成单元的测试。这里的最小组成单元可以是一个函数、一个类等等。
- 很多单元测试框架都有 watch 模式。每次改动代码时都会自动执行单元测试
- 频繁的执行意味着更早的发现问题。
- 随着代码的不断迭代,程序中总有某些位置会频繁出现某类问题。在没有单元测试时程序员之间往往都是“口口相传”,隔一段时间很可能由于疏忽还会犯同一个错误。
- 我们就可以为这些问题点编写对应的测试代码,每次提交代码前都执行一遍,可以极大的降低相同 bug 重复出现的概率。
- 如果你面对一个函数、类却很难编写测试代码的时候,很可能是你的代码设计上存在问题。比如和外部依赖耦合过于紧密。这种情况下,编写单元测试的过程会倒逼我们优化我们代码的结构。将复杂的代码拆解成为更简单、更容易测试的片段。这个过程本身也会潜移默化的提高我们代码的质量。
- 测试代码再简单,也是需要工作量来开发的
何时编写单元测试?
- 开发过程中,单元测试应该来测试那些可能会出错的地方,或是那些边界情况。
- 维护过程中,单元测试应该围绕着 bug 进行,每个 bug 都应该编写响应的单元测试。从而保证同一个 bug 不会出现第二次。
某个单元测试,往往包含以下几个步骤:
- 准备阶段:构造参数,创建 spy 等
- 执行阶段:用构造好的参数执行被测试代码
- 断言阶段:用实际得到的结果与期望的结果比较,以判断该测试是否正常
- 清理阶段:清理准备阶段对外部环境的影响,移除在准备阶段创建的 spy 等
易于测试的代码,说明是一个好的设计
- 测试、功能开发相结合,有利于设计和代码重构
- 集成了测试执行器、断言库、spy、mock、snapshot和测试覆盖率报告等功能
mock
function forEach(items, callback) {
for (let index = 0; index < items.length; index++) {
callback(items[index]);
}
}
describe("forEach", () => {
it("should call callback with each item", () => {
const mockFn = jest.fn();
forEach([1, 2], mockFn);
expect(mockFn.mock.calls.length).toBe(2);
expect(mockFn.mock.calls[0][0]).toBe(1);
expect(mockFn.mock.calls[1][0]).toBe(2);
});
});
spy
describe("bot", () => {
it("should say hello", () => {
const spy = jest.spyOn(bot, "sayHello");
bot.sayHello("Michael");
expect(spy).toHaveBeenCalledWith("Michael");
spy.mockRestore();
});
});
Jest是Facebook开发的一个测试框架
- 它集成了测试执行器、断言库、spy、mock、snapshot和测试覆盖率报告等功能。React项目本身也是使用Jest进行单测的,因此契合度相当高。
- Enzyme是由airbnb开发的React单元测试工具。它扩展了React的TestUtils并通过支持类似jQuery的find语法可以很方便的对render出来的结果做各种断言。
- "断言",就是判断源码的实际执行结果与预期结果是否一致,如果不一致就抛出一个错误。
- 采用Jest+Enzyme实现前端代码的单元测试
"scripts": {
"test": "jest --coverage", // 显示测试覆盖率
},
- React 组件的单元测试中我们需要找到合适的方法对执行结果进行断言
- 如何执行一个 React 组件并编写断言
- react-test-renderer 则负责将组件输出成 JSON 对象以方便我们遍历、断言或是进行 snapshot 测试。
- shallow render 比较适合验证组件输出的结构是否符合预期
- 通过 .find() 方法查找了 className 为 my-link 的 a 标签并确保找到了 3 个。
describe('Button', () => {
it('should be throttled to 200ms', () => {
const wrapper = mount(<Button>hello</Button>);
wrapper.find('.my-button').simulate('click');
expect(wrapper.state('disabled')).toBeTruthy();
jest.advanceTimersByTime(199);
expect(wrapper.state('disabled')).toBeTruthy();
jest.advanceTimersByTime(1);
expect(wrapper.state('disabled')).toBeFalsy();
});
it('should call onClick callback if provided', () => {
const onClickMock = jest.fn();
const wrapper = mount(<Button onClick={onClickMock}>hello</Button>);
wrapper.find('.my-button').simulate('click');
expect(onClickMock).toHaveBeenCalled();
});
});
enzyme中有几个比较核心的函数
- simulate(event, mock):用来模拟事件触发,event为事件名称,mock为一个event object;
- instance():返回测试组件的实例;
- find(selector):根据选择器查找节点,selector可以是CSS中的选择器,也可以是组件的构造函数,以及组件的display name等;
- at(index):返回一个渲染过的对象;
- get(index):返回一个react node,要测试它,需要重新渲染;
- contains(nodeOrNodes):当前对象是否包含参数重点 node,参数类型为react对象或对象数组;
- text():返回当前组件的文本内容;
- html(): 返回当前组件的HTML代码形式;
- props():返回根组件的所有属性;
- prop(key):返回根组件的指定属性;
- state():返回根组件的状态;
- setState(nextState):设置根组件的状态;
- setProps(nextProps):设置根组件的属性;
浅渲染shallow
- Shallow Rendering用于将一个组件渲染成虚拟DOM对象
- 不需要DOM环境,因为根本没有加载进DOM
- 只渲染第一层,处理速度非常快
完全渲染mount
- 将React组件加载为真实DOM节点
- 用jsdom模拟一个浏览器环境
静态渲染render
- 将React组件渲染成静态的HTML字符串
- 使用Cheerio这个库解析这段字符串,并返回一个Cheerio的实例对象,可以用来分析组件的html结构