前言
自动化测试
自动化测试有三种类型:Unit(单元测试)、Integration(集成测试)、End-to-end(端到端测试)
单元测试
: 单元测试是测试一个模块,不依赖任何外部资源集成测试
: 测试一个模块或者多个模块,并伴随着它们对应的外部依赖资源,它测试的是应用代码的集成性,比如文件或者数据库。端对端测试
: 依靠用户界面来驱动测试,这类测试可以保证很高的可靠性,但是它有两个很大的问题,第一个问题就是太慢了。因为每次测试都需要加载用户界面,每次测试都需要加载应用,也许还要用户登录,导航到指定页面,提交表单并检查结果,等等一系列,所以测试效率非常的慢 第二个问题就是太脆弱,因为一个程序、页面修改一下,如果 UI 变了,操作逻辑变了,就会彻底破坏这种测试
测试框架
测试框架给了我们一个包含很多工具的库,同时还给了我们一个测试的运行器
测试运行器会运行我们的测试,最常见的 3 个测试框架是 Jasmine、Mocha 和 Jest
Jasmine
(茉莉) 是一个比较早期的参与者,无需插件Mocha
(摩卡) 曾经是 npm 上最受欢迎的框架,但是它设计的时候就没有包含其他框架的一些功能,所以需要开发者去添加插件,比较常见的插件是 Chai 和 SinonJest
(玩笑) 是 Facebook 出品,是目前主流的测试框架,基本上是 Jasmine 的一个翻版,如果用惯了 Jasmine 上手 Jest 也很快
jest
Jest 是一款优雅、简洁的 JavaScript 测试框架。
无需配置
: Jest 的目标是在大多数 JavaScript 项目中即装即用,无需配置。快照
: 轻松编写持续追踪大型对象的测试,并在测试旁或代码内显示实时快照。隔离的
: 并行进行测试,发挥每一丝算力。优秀接口
: 从 it 到 expect - Jest 将工具包整合在一处。文档齐全、不断维护,非常不错。
基本使用
// 1.安装:yarn add --dev jest
// 2.index.js
function sum(a, b) {
return a + b;
}
export default sum
// 3.sum.test.js
import sum from './index'
test('renders learn react link', () => {
expect(sum(2,5)).toBe(7)
});
命令解释:
- jest --watchAll # 执行所有测试并时时监听文件
- jest --watch # 相当于执行 runs jest -o
执行结果相关:
% stmts
是语句覆盖率(statement coverage):每个语句是否都执行了% Branch
分支覆盖率(branch coverage):条件语句是否都执行了% Funcs
函数覆盖率(function coverage):函数是否全都调用了% Lines
行覆盖率(line coverage):未执行的代码行数
命令行交互模式提示相关:
- Press
f
to run only failed tests- f: 只会去跑之前没有通过的测试,修改完文件,在控制台按 f 就行
- Press
o
to only run tests related to changed files- o 模式,它只会测试当前改变的文件,一定要配合 git 使用,–watchAll 改为 --watch,默认直接进入 o 模式
- Press
p
to filter by a filename regex pattern- p:按测试文件名称的正则表达式来过滤哪些测试用例要执行
- Press
t
to filter by a test name regex pattern- t:按测试用例名称的正则表达式来过滤哪些测试用例要执行
- Press
q
to quit watch mode- q: 就直接是退出对代码的监控
- Press
Enter
to trigger a test run- Enter: 重新运行测试
基本命令
# 测试命令
jest
# 初始化:基于您的项目,Jest将向您提出几个问题,并将创建一个基本的配置文件
jest --init # js
npx ts-jest config:init # ts
# 指定配置文件为 xxx.js 进行测试
jest --config xxx.js
# 指定测试单个文件
jest components/button/__test__/button.test.js
# 指定测试单个组件
jest components/button/*
配合 babel
// babel.config.js
// 文件用于配置与你当前Node版本兼容的Babel
// 需要的包:@babel/core, @babel/preset-env
module.exports = {
presets: [
['@babel/preset-env', {targets: {node: 'current'}}],
// '@babel/preset-typescript', 支持 ts
],
};
基本语法
匹配
- toBe:object.is 相当于 ===
test('测试加法 3 + 7', () => {
// toBe 匹配器 matchers object.is 相当于 ===
expect(10).toBe(10)
})
- toEqual:内容相等,匹配内容,不匹配引用
test('toEqual 匹配器', () => {
// toEqual 匹配器 只会匹配内容,不会匹配引用
const a = { one: 1 }
expect(a).toEqual({ one: 1 })
})
toBeNull
:只匹配 NulltoBeUndefined
:只匹配 undefined,toBeDefined
相反,匹配 null 是通过的toBeTruthy
:匹配任何 if 语句为 true (1、true…),toBeFalsy
与其相反not
:取反
test('not 匹配器', () => {
const a = 1
// 以下两个匹配器是一样的
expect(a).not.toBeFalsy()
expect(a).toBeTruthy()
})
toBeGreaterThan
:大于;toBeLessThan
:小于;toBeGreaterThanOrEqual
:大于等于;toBeLessThanOrEqual
:小于等于;toBeCloseTo
:计算浮点数toMatch
: 匹配某个特定项字符串,支持正则
test('toMatch', () => {
const str = 'http://www.zsh.com'
expect(str).toMatch('zsh')
expect(str).toMatch(/zsh/)
})
toContain
:匹配是否包含某个特定项
test('toContain', () => {
const arr = ['z', 's', 'h']
const data = new Set(arr)
expect(data).toContain('z')
})
jest 的钩子函数
beforeAll
:在所有测试用例运行之前,会先调用 beforeAll 钩子函数beforeEach
:每个测试用例执行之前,都会调用,类似 vue-router 的 beforeEach,这样每次测试都是一个全新的实例,各个用例之间互不干扰。afterEach
:与 beforeEach 相反afterAll
:与 beforeAll 相反
jest 钩子函数的作用域
- describe:把增加相关的代码放到一类分组中,相减的放到另一类分组中,可以使用 describe 分组 ,Jest 默认会在最外层套一个 describe
- 不要在 describe 中写初始化的代码,避免踩坑,一定要写到钩子函数里
快照
- 使用
toMatchSnapshot
匹配器 - 运行后会在根目录生成一个
__snapshots__
文件夹 - 修改内容后与上次生成的快照进行比较,执行
jest --updateSnapshot
或jest -u
可更新快照 toMatchInlineSnapshot
会将快照生成到行内
// toMatchSnapshot
test('测试快照', () => {
expect(...).toMatchSnapshot()
})
// toMatchInlineSnapshot
it('renders correctly', () => {
// `` 中的内容是执行完后生成的
expect(...).toMatchInlineSnapshot(`...`);
});
测试 React
你可以用像测试其他 JavaScript 代码类似的方式测试 React 组件。
现在有许多种测试 React 组件的方法。大体上可以被分为两类:
- 渲染组件树 在一个简化的测试环境中渲染组件树并对它们的输出做断言检查。
- 运行完整应用 在一个真实的浏览器环境中运行整个应用(也被称为“端到端(end-to-end)”测试)。
日前两种主要方法:Enzyme + Jest
和 React Testing Library + Jest
,官方推荐 React Testing Library + Jest
的方式
Enzyme
Enzyme(酶) 是 Airbnb开源的 React 测试类库 Enzyme 提供了一套简洁强大的 API,并通过 jQuery 风格的方式进行DOM 处理,开发体验十分友好。
Shallow 浅渲染
it('shallow`', () => {
const wrapper = shallow(<MyComponent />);
expect(wrapper.find('.icon-star')).to.have.length(1);
});
it('renders children when passed in', () => {
const wrapper = shallow(
<MyComponent>
<div className="unique" />
</MyComponent>
);
expect(wrapper.contains(<div className="unique" />)).to.equal(true);
});
it('simulates click events', () => {
const onButtonClick = sinon.spy();
const wrapper = shallow(
<Foo onButtonClick={onButtonClick} />
);
wrapper.find('button').simulate('click');
expect(onButtonClick).to.have.property('callCount', 1);
});
Mount 完整的DOM渲染
it('mount', () => {
const wrapper = mount(<MyComponent bar="baz" />);
expect(wrapper.props().bar).to.equal('baz');
wrapper.setProps({ bar: 'foo' });
expect(wrapper.props().bar).to.equal('foo');
});
it('simulates click events', () => {
const onButtonClick = sinon.spy();
const wrapper = mount(
<Foo onButtonClick={onButtonClick} />
);
wrapper.find('button').simulate('click');
expect(onButtonClick).to.have.property('callCount', 1);
});
it('calls componentDidMount', () => {
sinon.spy(Foo.prototype, 'componentDidMount');
const wrapper = mount(<MyComponent />);
expect(Foo.prototype.componentDidMount).to.have.property('callCount', 1);
Foo.prototype.componentDidMount.restore();
});
Render 静态渲染
it('render', () => {
const wrapper = render(<MyComponent />);
expect(wrapper.find('.xxx').length).to.equal(3);
});
it('renders the title', () => {
const wrapper = render(<MyComponent title="unique" />);
expect(wrapper.text()).to.contain('unique');
});
快照
使用 enzyme-to-json
序列化进行快照比较,可在 jest.config.js 中进行配置 snapshotSerializers: ['enzyme-to-json/serializer']
,也可直接使用
import Counter from './counter';
it('测试快照', () => {
const {asFragment} = render(<Counter/>);
expect(asFragment(<Counter />)).toMatchSnapshot();
});
- React 16 中使用需要
enzyme-adapter-react-16
testing library
React 测试库是一组能让你不依赖 React 组件具体实现对他们进行测试的辅助工具。它让重构工作变得轻而易举,还会推动你拥抱有关无障碍的最佳实践。虽然它不能让你省略子元素来浅(shallowly)渲染一个组件,但像 Jest 这样的测试运行器可以通过 mocking 让你做到。
使用
AAA模式:编排(Arrange),执行(Act),断言(Assert)。
import React from "react";
import { render, fireEvent } from "@testing-library/react";
import Counter from "./app";
describe("<Counter />", () => {
it("properly increments the counter", () => {
// Arrange
const { getByText } = render(<Counter />);
const counter = getByText("0");
const incrementButton = getByText("+");
const decrementButton = getByText("-");
// Act
fireEvent.click(incrementButton);
// Assert
expect(counter.textContent).toEqual("1");
// Act
fireEvent.click(decrementButton);
// Assert
expect(counter.textContent).toEqual("0");
});
});
编排(Arrange)
-
getByLabelText:搜索与作为参数传递的给定文本匹配的标签,然后查找与该标签关联的元素。
-
getByText:搜索具有文本节点的所有元素,其中的textContent与作为参数传递的给定文本匹配。
-
getByTitle:返回具有与作为参数传递的给定文本匹配的title属性的元素。
-
getByPlaceholderText:搜索具有占位符属性的所有元素,并找到与作为参数传递的给定文本相匹配的元素。
-
getBy:返回查询的第一个匹配节点,如果没有匹配的元素或找到多个匹配,则抛出一个错误。
-
getAllBy:返回一个查询中所有匹配节点的数组,如果没有匹配的元素,则抛出一个错误。
-
queryBy:返回查询的第一个匹配节点,如果没有匹配的元素,则返回null。这对于断言不存在的元素非常有用。
-
queryAllBy:返回一个查询的所有匹配节点的数组,如果没有匹配的元素,则返回一个空数组([])。
-
findBy:返回一个promise,该promise将在找到与给定查询匹配的元素时解析。如果未找到任何元素,或者在默认超时时间为4500毫秒后找到了多个元素,则承诺将被拒绝。
-
findAllBy:返回一个promise,当找到与给定查询匹配的任何元素时,该promise将解析为元素数组。
执行(Act)
// fireEvent(node: HTMLElement, event: Event)
fireEvent.click(incrementButton);
断言(Assert)
expect(counter.textContent).toEqual("1");
expect(counter.textContent).toEqual("0");
快照
import {render, fireEvent} from '@testing-library/react';
it('测试快照', () => {
const {asFragment} = render(<Counter/>);
expect(asFragment(<Counter />)).toMatchSnapshot();
});
对比
为什么 Enzyme 应该被弃用?
原因很多,大体上可以概括为几个要点:
- 它长期以来一直落后于 React 的前进步伐,因此在阻碍人们过渡到更新的 React 版本
- 它依赖于 React 的内部实现,React 团队不鼓励使用它
- 它目前只由一个人维护——对于这么多使用它的公司来说,只依靠一个人来维护他们的一个关键软件是有风险的
- 它助长了一些糟糕的测试实践,并且 Enzyme 中的测试无法代表客户体验
- 市面上有了一个更好的解决方案,这个行业已经在前进了
- enzyme 需要搭载对应的版本进行使用,版本滞后会导致影响使用
- react 官网推荐 rtl
- enzyme 通过 find 查找类进行对比,rtl 可使用丰富的查询 api 进行对比
- enzyme 通过 simulate函数创建 DOM 事件 ,rtl 使用 userEvent
参考文献
jest 中文文档
Zsh’s Blog
react 中文文档
enzyme 官网
testing-library 官网
enzyme-to-json github
enzymejs github
testing-library github
是时候说再见了,Enzyme.js
使用 React Testing Library 和 Jest 完成单元测试
comparing-enzyme-with-react-testing-library