單元測試要求單元之間沒有耦合,每一個單元各司其職,這樣單元測試就會很明確,增長單元測試,和刪除單元測試都比較容易,因此函數式測試風格,可能更加好表達測試...javascript
單元測試只是手段,即便 100% 覆蓋率的單元測試,也不能保證程序是沒有 bug 的。html
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({ adapter: new Adapter() });
複製代碼
enzyme 有衆多的組合提供纔是vue
import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import sinon from 'sinon';
import MyComponent from './MyComponent';
import Foo from './Foo';
describe('<MyComponent />', () => {
it('renders three <Foo /> components', () => {
const wrapper = shallow(<MyComponent />);
expect(wrapper.find(Foo)).to.have.lengthOf(3);
});
it('renders an `.icon-star`', () => {
const wrapper = shallow(<MyComponent />);
expect(wrapper.find('.icon-star')).to.have.lengthOf(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);
});
});
複製代碼
import React from 'react';
import sinon from 'sinon';
import { expect } from 'chai';
import { mount } from 'enzyme';
import Foo from './Foo';
describe('<Foo />', () => {
it('allows us to set props', () => {
const wrapper = mount(<Foo 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(<Foo />);
expect(Foo.prototype.componentDidMount).to.have.property('callCount', 1);
Foo.prototype.componentDidMount.restore();
});
});
複製代碼
Vue 中的測試以 @vue/test-utils
做爲核心,測試時還須要其餘的 npm 包支持,和配置java
# jest
yarn add jest @vue/test-utils --dev
# vue@next 選擇一個適合版本,如今通常配合 @babel/core, 而非 babel-core
yarn add vue-jest@next --dev
# babel-jest
yarn add babel babel-jest
# jest 默認支持 commonjs 規範,若是咱們要使用 esmodule, 那麼就須要 babel 的插件轉換語法
yarn add @babel/plugin-transform-modules-commonjs --dev
複製代碼
根據配置,進行相應的配置:node
{
"scripts": {
"test": "jest --watch --coverage"
}
}
複製代碼
module.exports = {
collectCoverage: true,
collectCoverageFrom: [
"./src/**/*.{js, vue}",
"!node_modules/**"
],
coverageDirectory: './coverage',
coveragePathIgnorePatterns: [
"/node_modules/"
],
coverageReporters: [
"json",
"text",
"lcov",
"clover"
],
moduleFileExtensions: [
"js",
"json",
"jsx",
"ts",
"tsx",
"node",
"vue"
],
testMatch: [
"**/__tests__/**/*.[jt]s?(x)",
"**/?(*.)+(spec|test).[tj]s?(x)"
],
transform: {
// 用 `babel-jest` 處理 `*.js` 文件
"^.+\\.js$": "<rootDir>/node_modules/babel-jest",
// 用 `vue-jest` 處理 `*.vue` 文件
".*\\.(vue)$": "<rootDir>/node_modules/vue-jest"
},
transformIgnorePatterns: [
"/node_modules/"
]
};
複製代碼
咱們使用 Taro-Next
生成的項,根據測試環境給不一樣的 babel 配置,react
const isTest = process.env.NODE_ENV === 'test'
const presets = []
if (isTest) {
presets.concat([
'@babel/preset-env',
{
targets: {
chrome: 52,
},
},
])
} else {
presets.concat([
'taro',
{
framework: 'vue',
ts: false,
},
])
}
module.exports = {
presets: presets,
plugins: [
"@babel/plugin-transform-modules-commonjs"
]
}
複製代碼
基本工做基本完成,接下來就能夠快樂的進行測試了。git
接收一個 Vue 組件做爲參數。掛載以後就獲得了一個 包裹
了 組件
的 wrapper。這個 wrapper 具備訪問,操做組件的能力。github
<template>
<span>{{ message }}</span>
</template>
<script> export default { data () { return { message: 'hello!' } }, created () { this.message = 'bye!' } } </script>
複製代碼
// 導入 Vue Test Utils 內的 `shallowMount` 和待測試的組件
import { shallowMount } from '@vue/test-utils'
import MyComponent from './MyComponent.vue'
// 掛載這個組件
const wrapper = shallowMount(MyComponent)
// 這裏是一些 Jest 的測試,你也可使用你喜歡的任何斷言庫或測試
describe('MyComponent', () => {
// 檢查原始組件選項
it('has a created hook', () => {
expect(typeof MyComponent.created).toBe('function')
})
// 評估原始組件選項中的函數的結果
it('sets the correct default data', () => {
expect(typeof MyComponent.data).toBe('function')
const defaultData = MyComponent.data()
expect(defaultData.message).toBe('hello!')
})
// 檢查 mount 中的組件實例
it('correctly sets the message when created', () => {
expect(wrapper.vm.$data.message).toBe('bye!')
})
// 建立一個實例並檢查渲染輸出
it('renders the correct message', () => {
expect(wrapper.text()).toBe('bye!')
})
})
複製代碼
潛掛載組件,調用函數以後,會獲得一個 wrapper, wrapper 下面的一些方法是咱們須要掌握的。chrome
import { mount } from '@vue/test-utils'
import Icon from '../index'
test('has icon component', () => {
const wrapper = mount(Icon)
console.log("name ", wrapper.vm.name)
expect(wrapper.vm.name).toBe('arrow')
expect(wrapper.vm.width).toBe("30")
expect(wrapper.vm.height).toBe("30")
expect(wrapper.vm.color).toBe("#f00")
expect(wrapper.vm.iconCls).toBe("van-icon van-icon-arrow")
expect(wrapper.vm.iconStyle).toEqual({
width: '30px',
height: '30px',
lineHeight: '30px',
color: '#f00'
})
})
複製代碼
組件中中 props 包含了 name、width、height、color, 計算屬性: iconCls、iconStylesnpm
咱們須要給 mount 傳入第二個參數,一些選項數據:
test('props custom params', () => {
const wrapper = mount(Icon, {
propsData: {
name: 'good-job-o',
width: '40',
height: '40',
color: 'blue'
}
})
expect(wrapper.props().name).toBe('good-job-o') // 3 passed, 3 total
})
複製代碼
小程序能夠進行組件級別
的單元測試,也能夠進行頁面級別
的測試。
工具:miniprogram-simulate
安裝,組件化單元測試,不依賴小程序的運行時。讀取組件直接,進行測試。
注意:小程序的單元測試能力是頗有限的。
yarn add miniprogram-simulate --dev
複製代碼
// /test/components/index.test.js
const simulate = require('miniprogram-simulate')
test('components/index', () => {
const id = simulate.load('/components/index') // 此處必須傳入絕對路徑
const comp = simulate.render(id) // 渲染成自定義組件樹實例
const parent = document.createElement('parent-wrapper') // 建立父親節點
comp.attach(parent) // attach 到父親節點上,此時會觸發自定義組件的 attached 鉤子
const view = comp.querySelector('.index') // 獲取子組件 view
expect(view.dom.innerHTML).toBe('index.properties') // 測試渲染結果
expect(window.getComputedStyle(view.dom).color).toBe('green') // 測試渲染結果
})
複製代碼
頁面級別的測試叫小程序自動化
,自動化
能夠幫助完成不少重複的任務。
小程序爲自動化提供了一個重要的 SDK: miniprogram-automator
, 注意和小程序單元測試 miniprogram-simulate
是不同的。
miniprogram-automator
須要運行,配置啓動微信小程序開發工具。
const automator = require('miniprogram-automator')
automator.launch({
cliPath: 'path/to/cli', // 工具 cli 位置,若是你沒有更改過默認安裝位置,能夠忽略此項
projectPath: 'path/to/project', // 項目文件地址
}).then(async miniProgram => {
const page = await miniProgram.reLaunch('/page/component/index')
await page.waitFor(500)
const element = await page.$('.kind-list-item-hd')
console.log(await element.attribute('class'))
await element.tap()
await miniProgram.close()
})
複製代碼