前言
自动化测试
自动化测试有三种类型: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 将工具包整合在一处。文档齐全、不断维护,非常不错。
基本使用
function sum(a, b) {
return a + b;
}
export default sum
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
- Press
Enter to trigger a test run
基本命令
jest
jest --init
npx ts-jest config:init
jest --config xxx.js
jest components/button/__test__/button.test.js
jest components/button/*
配合 babel
module.exports = {
presets: [
['@babel/preset-env', {targets: {node: 'current'}}],
],
};
基本语法
匹配
test('测试加法 3 + 7', () => {
expect(10).toBe(10)
})
test('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/)
})
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 会将快照生成到行内
test('测试快照', () => {
expect(...).toMatchSnapshot()
})
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", () => {
const { getByText } = render(<Counter />);
const counter = getByText("0");
const incrementButton = getByText("+");
const decrementButton = getByText("-");
fireEvent.click(incrementButton);
expect(counter.textContent).toEqual("1");
fireEvent.click(decrementButton);
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.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
|