TA的每日心情 | 擦汗 前天 09:02 |
---|
签到天数: 1042 天 连续签到: 4 天 [LV.10]测试总司令
|
工具
单元测试要求单元之间没有耦合,每个单元各司其职,这样单元测试就会很明确,增加单元测试,和删除单元测试都比较容易,所以函数式测试风格,可能更加好表达测试...
单元测试只是手段,即使 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[url=home.php?mod=space&uid=24198]@next[/url] 选择一个适合版本,现在一般配合 @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()
- })
复制代码
|
|