一、前端测试简介及意义

简介

现在有许多种测试 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
  1. beforeAll(() => console.log('1 - beforeAll'));
  2. afterAll(() => console.log('1 - afterAll'));
  3. beforeEach(() => console.log('1 - beforeEach'));
  4. afterEach(() => console.log('1 - afterEach'));
  5. test('', () => console.log('1 - test'));
  6. describe('test', () => {
  7. beforeAll(() => console.log('2 - beforeAll'));
  8. afterAll(() => console.log('2 - afterAll'));
  9. beforeEach(() => console.log('2 - beforeEach'));
  10. afterEach(() => console.log('2 - afterEach'));
  11. test('', () => console.log('2 - test'));
  12. });
  13. // 1 - beforeAll
  14. // 1 - beforeEach
  15. // 1 - test
  16. // 1 - afterEach
  17. // 2 - beforeAll
  18. // 1 - beforeEach
  19. // 2 - beforeEach
  20. // 2 - test
  21. // 2 - afterEach
  22. // 1 - afterEach
  23. // 2 - afterAll
  24. // 1 - afterAll

断言

  1. expect(value):要测试一个值进行断言的时候,要使用expect对值进行包裹
  2. toBe(value):使用Object.is来进行比较
  3. not:用来取反
  4. toEqual(value):用于对象的深比较
  5. toMatch(regexpOrString):用来检查字符串是否匹配,可以是正则表达式或者字符串
  6. toContain(item):用来判断item是否在一个数组中,也可以用于字符串的判断
  7. toBeNull(value):只匹配null
  8. toBeUndefined(value):只匹配undefined
  9. toBeDefined(value):与toBeUndefined相反
  10. toBeTruthy(value):匹配任何使if语句为真的值
  11. toBeFalsy(value):匹配任何使if语句为假的值
  12. extend(matchers):自定义一些断言
  13. toBeGreaterThan(number): 大于
  14. toBeGreaterThanOrEqual(number):大于等于
  15. toBeLessThan(number):小于
  16. toBeLessThanOrEqual(number):小于等于
  17. toBeInstanceOf(class):判断是不是class的实例
  18. anything(value):匹配除了null和undefined以外的所有值
  19. resolves:用来取出promise为fulfilled时包裹的值,支持链式调用
  20. rejects:用来取出promise为rejected时包裹的值,支持链式调用
  21. toHaveBeenCalled():用来判断mock function是否被调用过
  22. toHaveBeenCalledTimes(number):用来判断mock function被调用的次数
  23. assertions(number):验证在一个测试用例中有number个断言被调用

Mock函数

在项目中,一个模块的方法内常常会去调用另外一个模块的方法。在单元测试中,我们可能并不需要关心内部调用的方法的执行过程和结果,只想知道它是否被正确调用即可,甚至会指定该函数的返回值。

Mock函数提供的以下三种特性

  • 捕获函数调用情况
  • 设置函数的返回值
  • 改变函数的内部实现

Enzyme三种渲染方法和常用API概述

  1. shallow:浅渲染,它将组件渲染成虚拟DOM对象,只会渲染第一层,子组件将不会被渲染出来,因而效率非常高。不需要DOM环境, 并可以使用jQuery的方式访问组件的信息;
  2. mount:完全渲染,它将组件渲染加载成一个真实的DOM节点,用来测试DOM API的交互和组件的生命周期,用到了jsdom来模拟浏览器环境。

PS: (mount方法用于将React组件加载为真实DOM节点。
然而真实DOM需要一个浏览器环境,为了解决这个问题,我们可以用到jsdom,我们可以用jsdom模拟一个浏览器环境去加载真实的DOM节点)

  1. 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