本文中的自动化测试指的是单元测试 (UT),所谓单元测试也就是对每一个单元进行测试,通俗的将通常针对的是函数,类或单个组件,不涉及系统和集成。单元测试是软件测试的基础测试,主要是用来验证所测代码是否和程序员的指望一致。css
jest 是 facebook 开源的,用来进行单元测试的框架,功能比较全面,测试、断言、覆盖率它均可以,另外还提供了快照功能。html
2.1安装node
安装jestreact
npm install --save-dev jest
安装babel-jestjquery
npm install --save-dev babel-jest
安装enzyme,须要根据项目的react版原本安装对应的enzymegit
npm install --save-dev enzyme enzyme-adapter-react-16
安装react-test-renderer程序员
npm install --save-dev react-test-renderer
2.2配置github
package.json中添加:npm
{ "scripts": { "test": "jest" } }
执行npm run test 命令可在终端运行查看测试运行结果。json
同时 Jest
还提供了生成测试覆盖率报告的命令,只须要添加上 --coverage
这个参数既可生成,再加上--colors可根据覆盖率生成不一样颜色的报告(<50%红色,50%~80%黄色, ≥80%绿色)
.babelrc文件中添加,请根据本身的项目状况调整
{ "env": { "test": { "presets": [["next/babel", { "preset-env": { "modules": "commonjs" }, "styled-jsx": { "plugins": [ "styled-jsx-plugin-postcss" ] } }]] } } }
jest.config.js: jest配置文件,可放在根目录下或config文件下(也能够起其余名字或者直接写在package.json里)
module.exports = { setupFiles: ['<rootDir>/jest.setup.js'], // 运行测试前可执行的脚本(好比注册enzyme的兼容) transform: { '^.+\\.(js|jsx|mjs)$': '<rootDir>/node_modules/babel-jest', '^.+\\.css$': '<rootDir>/__test__/css-transform.js', }, testPathIgnorePatterns: ['<rootDir>/.next/', '<rootDir>/node_modules/'], //转换时需忽略的文件 testURL: 'http://localhost/', // 运行环境下的URl };
还有一些配置, 详细的配置见jest官网
collectCoverage: true, // 是否收集测试时的覆盖率信息(默认是false,同package配置的--coverage参数) collectCoverageFrom: ['<rootDir>/src/**/*.{js,jsx,mjs}'], // 哪些文件须要收集覆盖率信息 coverageDirectory: '<rootDir>/test/coverage', // 输出覆盖信息文件的目录 coveragePathIgnorePatterns: ['/node_modules/', '<rootDir>/src/index.jsx'], // 统计覆盖信息时须要忽略的文件 moduleNameMapper: { // 须要mock处理掉的文件,好比样式文件 }, testMatch: [ // 匹配的测试文件 '<rootDir>/test/**/?(*.)(spec|test).{js,jsx,mjs}', '<rootDir>/src/**/__tests__/**/*.{js,jsx,mjs}', ],
jest.setup.js
/* eslint-disable import/no-extraneous-dependencies */ import { configure } from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; configure({ adapter: new Adapter() });
一般测试文件名与要测试的文件名相同,后缀为.test.js,全部测试文件默认放在__test__文件夹中。
describe块之中,提供测试用例的四个函数:before()、after()、beforeEach()和afterEach()。它们会在指定时间执行(若是不须要能够不写)
describe('加法函数测试', () => {
before(() => {// 在本区块的全部测试用例以前执行
});
after(() => {// 在本区块的全部测试用例以后执行
});
beforeEach(() => {// 在本区块的每一个测试用例以前执行
});
afterEach(() => {// 在本区块的每一个测试用例以后执行
});
it('1加1应该等于2', () => {
expect(add(1, 1)).toBe(2);
});
it('2加2应该等于4', () => {
expect(add(2, 2)).toBe(42);
});
});
测试文件中应包括一个或多个describe, 每一个describe中能够有一个或多个it,每一个describe中能够有一个或多个expect.
describe称为"测试套件"(test suite),it块称为"测试用例"(test case)。
expect就是判断源码的实际执行结果与预期结果是否一致,若是不一致就抛出一个错误.
import React from 'react'; export default () => ( <div>404</div> );
/* eslint-env jest */ import { shallow } from 'enzyme'; import React from 'react'; import Page404 from '../components/Page404'; describe('Page404', () => { it('Page404 shows "404"', () => { const app = shallow(<Page404 />); expect(app.find('div').text()).toEqual('404'); }); });
这个测试只测试了组件是否被正常显示出来了。expect
部分是断言,实现内容是在被渲染出的Page404组件中找到div标签,而后断言它的text()
中有没有包含指望的文字。经过这种方式咱们能够得知组件是否有被显示出来。
除了text()
属性之外,还可很是灵活的经过其余方式来得知组件是否被正常显示。例如:
expect(wrapper.find('.card').exists()).toBeTruthy()
expect(wrapper.find('input').props().type).toBe('text')
npm test运行全部测试文件或 npm test <name> 运行匹配的测试文件:
% Stmts是语句覆盖率(statement coverage):是否每一个语句都执行了
% Branch分支覆盖率(branch coverage):是否每一个分支代码块都执行了(if, ||, ? : )
% Funcs函数覆盖率(function coverage):是否每一个函数都调用了
% Lines行覆盖率(line coverage):是否每一行都执行了
enzyme是Airbnb开源的react测试类库,提供了一套简洁强大的API,并经过jquery风格的方式进行dom处理,开发体验十分友好. 它提供三种测试方法
shallow:
shallow 返回组件的浅渲染,对官方shallow rendering 进行封装。浅渲染 做用就是:它仅仅会渲染至虚拟dom,不会返回真实的dom节点,这个对测试性能有极大的提高。shallow只渲染当前组件,只能能对当前组件作断言
mount :
mount 方法用于将React组件加载为真实DOM节点。mount会渲染当前组件以及全部子组件
render:
render 采用的是第三方库Cheerio
的渲染,渲染结果是普通的html结构,对于snapshot使用render
比较合适。
多数状况下,shallow
方法就能知足咱们的需求了。
Enzyme的一部分API,你能够从中了解它的大概用法。详细的API
.get(index):返回指定位置的子组件的DOM节点
.at(index):返回指定位置的子组件
.first():返回第一个子组件
.last():返回最后一个子组件
.type():返回当前组件的类型
.text():返回当前组件的文本内容
.html():返回当前组件的HTML代码形式
.props():返回根组件的全部属性
.prop(key):返回根组件的指定属性
.state([key]):返回根组件的状态
.setState(nextState):设置根组件的状态
.setProps(nextProps):设置根组件的属性
例如:
expect(wrapper.find('input').prop('value')).toBe('default value');
/* eslint-env jest */ import { shallow } from 'enzyme'; import React from 'react'; import { OrderManage } from '../../components/purchaser/OrderManege'; const setup = ({ ...props }) => { const wrapper = shallow(<OrderManage {...props} />); return { props, wrapper, }; };
describe('OrderManage', () => { it('role is operator', () => { const { wrapper } = setup({ role: 'operator', isFetching: true, fetchOrdersByStatuses: () => {}, // 直接设为空函数
getData: jest.fn(), // Jest 提供的mock 函数 }); const params = { node: { id: 2, }, }; expect(wrapper.instance().handlePageChange(1)); expect(wrapper.instance().OrderManagementLink(params)); expect(wrapper.find('.loader')).toHaveLength(1); expect(wrapper.find('.order-simpleGrid')).toHaveLength(0); expect(wrapper.type()).toEqual('div'); }); });
在正式测试功能以前,咱们要写一个 setup
方法用来渲染组件,由于每个测试case都会用到它
export class Card extends React.Component { constructor (props) { super(props) this.cardType = 'initCard' } changeCardType (cardType) { this.cardType = cardType } ... }
it('changeCardType', () => { let component = shallow(<Card />) expect(component.instance().cardType).toBe('initCard') component.instance().changeCardType('testCard') expect(component.instance().cardType).toBe('testCard') })
其中,instance
方法能够用于获取组件的内部成员对象。
<Input value={value} onChange={e => this.handleChange(e)}/>
it('can save value and cancel', () => { const value = 'edit' const {wrapper, props} = setup({ editable: true }); wrapper.find('input').simulate('change', {target: {value}}); wrapper.setProps({status: 'save'}); expect(props.onChange).toBeCalledWith(value); })
咱们能够在这个返回的 dom 对象上调用相似 jquery 的api进行一些查找操做,还能够调用 setProps 和 setState 来设置 props 和 state,也能够用 simulate 来模拟事件,
触发事件后,去判断props上特定函数是否被调用,传参是否正确;组件状态是否发生预料之中的修改;某个dom节点是否存在是否符合指望。
例:
wrapper.find('button').simulate('click');
wrapper.find('input').simulate('keyup');
expect(props.onClick).toBeCalled();// onClick方法被调用
expect(props.onClick).not.toBeCalled() // onClick方法没被调用
对于
可使用 Enzyme 中的 shallow
方法加载组件,例如
it('componentWillMount', () => { sinon.spy(App.prototype, 'componentWillMount'); shallow(<App />); expect(App.prototype.componentWillMount.calledOnce).toBeTruthy(); });
it('componentWillReceiveProps', () => { let wrapper = shallow(<App role={''} />); sinon.spy(App.prototype, 'componentWillReceiveProps') wrapper.setProps({ role: 'admin' }); expect(App.prototype.componentWillReceiveProps.calledOnce).toBeTruthy(); })
其中,spy 是 sinon 提供的特殊函数,它能够获取关于函数调用的信息。例如,调用函数的次数、每次调用的参数、返回的值、抛出的错误等,能够用来测试一个函数是否被正确地调用。npm i --dave-dev sinon 安装sinon.
而对于
要用enzyme的mount方法进行加载。
import renderer from 'react-test-renderer' it('App -- snapshot', () => { const renderedValue = renderer.create(<App />).toJSON() expect(renderedValue).toMatchSnapshot() })
jest的特点, 快照测试第一次运行的时候会将 React 组件在不一样状况下的渲染结果(挂载前)保存一份快照文件。后面每次再运行快照测试时,都会和第一次的比较,diff出两次快照的变化。
若是须要更新快照文件,使用 npm run test -- -u
命令
redux官网有详细的例子,送上传送门。
上面主要介绍了UT的安装配置及几个测试demo,之前没有接触过单元测试,各类踩坑与啃读API(jest + enzyme),这些demo基本能够知足项目中的测试,后续在写测试中再进步。刚开始接触测试是一点思路也没有,看见组件后无从下手,也一直在思考花费这么多时间写测试到底值不值得,下面是目前遇到的问题和一些思考中的问题,能够一块儿讨论一下: