工具
单元测试要求单元之间没有耦合,每个单元各司其职,这样单元测试就会很明确,增加单元测试,和删除单元测试都比较容易,所以函数式测试风格,可能更加好表达测试...
单元测试只是手段,即使 100% 覆盖率的单元测试,也不能保证程序是没有 bug 的。
jest 一个功能齐全的测试 javascript 测试框架, mock、覆盖率、Snapshot快照、执行器、断言库和间谍 spy 功能等等...
enzyme 是 Airbnb 出品, 是一个工具库,让我们更方便的遍历、操作 React 组件输出的内容
enzyme-adapter-react-16
@vue/test-utils Vue 组件测试工具
miniprogram-simulate 微信小程序的自动化和单元测试
React 测试
配置
enzyme 有众多的组合提供才是
import Enzyme from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; Enzyme.configure({ adapter: new Adapter() }); |
chai-enzyme with Mocha/Chai.
jasmine-enzyme with Jasmine.
jest-enzyme with Jest.
should-enzyme for should.js.
expect-enzyme for expect.
官方示例
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 中的测试以 @vue/test-utils 作为核心,测试时还需要其他的 npm 包支持,和配置。
其他的 npm 包
# 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 |
根据配置,进行相应的配置:
npm 脚本启动
{ "scripts": { "test": "jest --watch --coverage" } } |
配置 jest
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/" ] }; |
babel 配置
我们使用 Taro-Next 生成的项,根据测试环境给不同的 babel 配置。
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" ] } |
基本工作基本完成,接下来就可以快乐的进行测试了。
基础 API
mount 挂载组件
shallowMount 潜挂载组件
接收一个 Vue 组件作为参数。挂载之后就得到了一个 包裹了 组件 的 wrapper。这个 wrapper 具有访问,操作组件的能力。
官方示例
<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 下面的一些方法是我们需要掌握的。
wrapper.vm
wrapper.text()
wrapper.html() 组件渲染出的 html
wrapper.contains('xxx') 已包含的元素
wrapper.find('xxx') 找到元素,模拟用户交互
测试 Vue Props
测试 props 和 computed 的默认值。
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、iconStyles
测试 props 的传入值
我们需要给 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() }) |
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理