一、前端测试简介及意义
简介
现在有许多种测试 React 组件的方法。大体上可以被分为两类:
- 渲染组件树: 在一个简化的测试环境中渲染组件树并对它们的输出做断言检查——单元测试
- 运行完整应用: 在一个真实的浏览器环境中运行整个应用(也被称为“端到端(end-to-end)”测试)——黑盒测试
单元测试: 针对程序模块进行测试。模块是软件设计中的最小单位,一个函数或者一个 React 组件都可以称之为一个模块。基本特征就是只要输入不变,必定返回同样的输出。一个软件越容易写单元测试,说明模块间耦合度越低。
意义
- 保障代码质量: 测试可以确保得到预期的结果;促使开发者写可测试的代码,一般可测试的代码可读性也会高一点
- 提升开发效率: 在开发过程中进行测试能让我们提前发现 bug ,此时进行问题定位和修复的速度自然比开发完再被叫去修 bug 要快许多
- 便于项目维护: 后续任何代码更新也必须跑通测试用例,即使进行重构或开发人员发生变化也能保障预期功能的实现
二、技术选型
Jest + Enzyme
Jest
Jest 是 Facebook 开源的测试框架,主要用于 React 和 React Native 的单元测试,已被集成在 create-react-app 中,可开箱即用。它的功能很强大,包含了测试执行器、断言库、spy、mock、snapshot 和测试覆盖率报告等
Enzyme
Enzyme是 Airbnb 开源的 React 单元测试工具。它扩展了 React 官方的 TestUtils,通过类 jQuery 风格的 API 对 DOM 进行处理,减少了很多重复代码,可以很方便的对渲染出来的结果进行断言。
- Enzyme 的定位是一个工具库
- Enzyme 的出现是为了让我们更方便的遍历、操作 React 组件输出的内容
三、举例应用
第一个例子
看代码firstDemo
Jest常用API(Globals API、断言、Mock函数)概述
Globals API
- describe(name, fn):描述块,将一组功能相关的测试用例组合在一起
- it(name, fn, timeout):别名test,用来放测试用例
- afterAll(fn, timeout):所有测试用例跑完以后执行的方法
- beforeAll(fn, timeout):所有测试用例执行之前执行的方法
- afterEach(fn):在每个测试用例执行完后执行的方法
- beforeEach(fn):在每个测试用例执行之前需要执行的方法
后四个 API 即为预处理和后处理,譬如环境的检查、或者在测试结束之后做一些清理操作等,其执行优先级:
- beforeAll > beforeEach;Global > describe
- afterEach > afterAll;describe > Global
beforeAll(() => console.log('1 - beforeAll'));
afterAll(() => console.log('1 - afterAll'));
beforeEach(() => console.log('1 - beforeEach'));
afterEach(() => console.log('1 - afterEach'));
test('', () => console.log('1 - test'));
describe('test', () => {
beforeAll(() => console.log('2 - beforeAll'));
afterAll(() => console.log('2 - afterAll'));
beforeEach(() => console.log('2 - beforeEach'));
afterEach(() => console.log('2 - afterEach'));
test('', () => console.log('2 - test'));
});
// 1 - beforeAll
// 1 - beforeEach
// 1 - test
// 1 - afterEach
// 2 - beforeAll
// 1 - beforeEach
// 2 - beforeEach
// 2 - test
// 2 - afterEach
// 1 - afterEach
// 2 - afterAll
// 1 - afterAll
断言
- expect(value):要测试一个值进行断言的时候,要使用expect对值进行包裹
- toBe(value):使用Object.is来进行比较
- not:用来取反
- toEqual(value):用于对象的深比较
- toMatch(regexpOrString):用来检查字符串是否匹配,可以是正则表达式或者字符串
- toContain(item):用来判断item是否在一个数组中,也可以用于字符串的判断
- toBeNull(value):只匹配null
- toBeUndefined(value):只匹配undefined
- toBeDefined(value):与toBeUndefined相反
- toBeTruthy(value):匹配任何使if语句为真的值
- toBeFalsy(value):匹配任何使if语句为假的值
- extend(matchers):自定义一些断言
- toBeGreaterThan(number): 大于
- toBeGreaterThanOrEqual(number):大于等于
- toBeLessThan(number):小于
- toBeLessThanOrEqual(number):小于等于
- toBeInstanceOf(class):判断是不是class的实例
- anything(value):匹配除了null和undefined以外的所有值
- resolves:用来取出promise为fulfilled时包裹的值,支持链式调用
- rejects:用来取出promise为rejected时包裹的值,支持链式调用
- toHaveBeenCalled():用来判断mock function是否被调用过
- toHaveBeenCalledTimes(number):用来判断mock function被调用的次数
- assertions(number):验证在一个测试用例中有number个断言被调用
Mock函数
在项目中,一个模块的方法内常常会去调用另外一个模块的方法。在单元测试中,我们可能并不需要关心内部调用的方法的执行过程和结果,只想知道它是否被正确调用即可,甚至会指定该函数的返回值。
Mock函数提供的以下三种特性
- 捕获函数调用情况
- 设置函数的返回值
- 改变函数的内部实现
Enzyme三种渲染方法和常用API概述
- shallow:浅渲染,它将组件渲染成虚拟DOM对象,只会渲染第一层,子组件将不会被渲染出来,因而效率非常高。不需要DOM环境, 并可以使用jQuery的方式访问组件的信息;
- mount:完全渲染,它将组件渲染加载成一个真实的DOM节点,用来测试DOM API的交互和组件的生命周期,用到了jsdom来模拟浏览器环境。
PS: (mount方法用于将React组件加载为真实DOM节点。
然而真实DOM需要一个浏览器环境,为了解决这个问题,我们可以用到jsdom,我们可以用jsdom模拟一个浏览器环境去加载真实的DOM节点)
- render:静态渲染,它将React组件渲染成静态的HTML字符串,然后使用Cheerio这个库解析这段字符串,并返回一个Cheerio的实例对象,可以用来分析组件的html结构。
PS:(render 性能比 mount好,那么问题来了,mount存在的价值是什么,render就可以测试子组件,render还不需要jsdom和额外的配置。
当然是有价值的,shallow和mount因为都是dom对象的缘故,所以都是可以模拟交互的)
渲染方法 | 是否可以测试子组件 | 是否可以模拟交互 | 性能 |
---|---|---|---|
shallow | 否 | 是 | 高 |
mount | 是 | 是 | 低 |
render | 是 | 否 | 中 |
常用API
- 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):设置根组件的属性;
四、好处
- 保障代码质量和功能的实现的完整度
- 提升开发效率,在开发过程中进行测试能让我们提前发现 bug ,此时进行问题定位和修复的速度自然比开发完再被叫去修 bug 要快许多
- 便于项目维护,后续任何代码更新也必须跑通测试用例,即使进行重构或开发人员发生变化也能保障预期功能的实现
五、适用场景
- 需要长期维护的项目。它们需要测试来保障代码可维护性、功能的稳定性
- 较为稳定的项目、或项目中较为稳定的部分。给它们写测试用例,维护成本低
- 被多次复用的部分,比如一些通用组件和库函数。因为多处复用,更要保障质量
六、参考:
https://juejin.im/post/597aa518f265da3e345f3262#heading-13
1 为 React 编写单元测试之一 —— 测试框架的选择
https://loveky.github.io/2018/04/02/write-unit-test-for-react-part1-choose-test-framwork
2 单元测试与单元测试框架 Jest
https://loveky.github.io/2018/05/17/unit-test-and-jest
3 利用 Jest 为 React 组件编写单元测试
https://loveky.github.io/2018/06/05/unit-testing-react-component-with-jest
4 使用jest+enzyme进行react项目测试 - 测试手法篇
http://echizen.github.io/tech/2017/02-12-jest-enzyme-method
5 Jest 入门指南
http://iceiceice.top/2018/12/29/introduce-jest/
七、自动化测试
cucumber + puppeteer
https://www.jianshu.com/p/3857f2c3a8d4