前言
本文主要是介绍基于React Ant Design(以下用Antd表示Ant Design)的项目,在对于自己封装的,或者基于Antd封装的公共组件的自动化测试技术的选型和实践。
背景
当前前端项目越来越大,业务逻辑日益繁杂,协同开发的同事也越来越多,迭代频繁,许多页面有一些相似的功能,会复用一些组件,这些组件被剥离出来,一般放在component
文件夹下,大家共同维护,这时会出现一些常见问题:
- 保证当前组件的质量,即当前业务的正常使用
- 在新需求下,旧的组件如果能满足新需求50%以上的功能,应当升级旧组件满足新需求,同时兼容旧业务
- 除该组件Owner之外第二人,在修改组件的过程中,避免因为对代码的不熟悉,改出BUG
- 一个组件多个页面复用,修改后的测试回归任务重
技术选型
目前前端整体的测试框架较为常用的有:
- Jest
- Mocha
Jest
源自Facebook,Jest 的一个理念是提供一套完整集成的 “零配置” 测试体验。
- 包含单元测试运行器、断言库、Mock库
- 内置代码覆盖率报告
- 可以与Typescript一同使用
- 零配置,开箱即用
Mocha
仅仅是测试运行器,虽然灵活,但需要自己配置很多东西。
React项目测试选型
- react-addons-test-utils:官方API,有些晦涩
- Enzyme:源自Airbnb,封装了React官方测试API,类Jquery风格简洁的API, 使得Dom操作变得十分友好
综合目前市面上的轮子,我们技术选型为Jest Enzyme
实践
例子是一个基于Antd二次封装的单选年的日期选择器,如下演示:
代码结构如下
其中测试相关的文件,在test中,后缀名为xxx.test.js的文件,在运行测试时会自动执行,snapshots为自动生成的页面快照。
这里可以首先简单的看一下,Jest Enzyme的基本语法:
Jest的API更多着力于定义测试、断言、mock库
定义测试:
- describe: 定义一个测试套件(test suite)
- it: 定义一个测试(test)
- beforeEach: 定义一个回调函数在每个测试之前执行
- expect: 执行一个断言
- jest.fn(): 创造一个mock函数
一些用于断言的方法:
- toEqual: 验证两个值是否相同
- toBe: 验证两个值是否 === 完全相等
- toHaveLength:验证长度
- toBeDefined: 验证一个值是否被定义
- toContain: 验证一个list中是否包含某一项
- toBeCalled: 验证一个mock函数是否被调用
- toBeCalledWith: 验证一个mock函数是否被传入指定的参数被调用
一些用于mock的方法:
- mockImplementation: 提供mock函数的执行
- mockReturnValue: mock函数被调用返回一个值
Enzyme的API更多着重于渲染react组件和从dom树种检索指定的节点
下面是三种渲染组件的方法:
- shallow: 会渲染至虚拟dom,不会返回真实的dom节点,大幅提升测试性能
- mount: 实现Full Rendering 比如说当我们需要对DOM API交互或者你需要测试组件的整个生命周期的时候,需要使用这个方法。
- render: 渲染出最终的html,然后利用这个html结构来进行分析处理
一些被渲染的组件检索节点的方法:
- find: 通过匹配选择器来检索节点
- some: 当至少有一个节点匹配选择器是返回true
- first: 返回集合的第一个节点
- at: 返回集合的第n个节点
- html: 获取节点的HTML结构
- text: 获取节点的文本
一些用于组件交互的方法:
- simulate: 模拟一个事件
- setProps: 设置props
- setState: 设置state
- props(key): 用于检索组件的props
- state(key): 用于检索组件的state
具体的写法,index.test.js文件内容如下:
代码语言:javascript复制import React, { PureComponent } from 'react';
import { mount, ReactWrapper, render } from 'enzyme';
import YearPicker from '..';
import moment from 'moment';
class YearPickerDemo extends React.Component {
state = {
cleared: false,
value: moment().format('YYYY'),
};
render() {
return (
<YearPicker
showTime
format="YYYY"
onChange={this.onChange}
defaultValue={moment('2015/01/01', 'YYYY')}
/>
);
}
}
describe('DatePicker', () => {
it('default value', () => {
const wrapper = mount(<YearPickerDemo/>);
expect(wrapper.find('.ant-calendar-picker-input').getDOMNode().value).toBe('2015');
});
it('clear value', () => {
const wrapper = mount(<YearPickerDemo/>);
wrapper.find('.ant-calendar-picker-clear').hostNodes().simulate('click');
expect(wrapper.find('.ant-calendar-picker-input').getDOMNode().value).toBe('');
});
it('set value in calendar', () => {
const wrapper = mount(<YearPickerDemo/>);
wrapper.find('.ant-calendar-picker-input').simulate('click');
const triggerWrapper = mount(wrapper.find('Trigger').instance().getComponent());
triggerWrapper.find('[title="2018"]').simulate('click');
expect(wrapper.find('.ant-calendar-picker-input').getDOMNode().value).toBe('2018');
});
});
这里定义了3个测试内容
- 测试默认值,即检查输入框的值是否为默认值
- 测试清除按钮是否可用,通过模拟点击清除按钮,测试是否能按照预期清除输入框内填充的默认值
- 测试设置值,点击输入框,弹出选择框,选择值,检查输入框中的值是否为选择的值