本文中的自動化測試指的是單元測試 (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基本能夠知足項目中的測試,後續在寫測試中再進步。剛開始接觸測試是一點思路也沒有,看見組件後無從下手,也一直在思考花費這麼多時間寫測試到底值不值得,下面是目前遇到的問題和一些思考中的問題,能夠一塊兒討論一下: