三种工具简介
-
Karma
Karma官方网址
Karma为前端自动化测试提供了跨浏览器测试的能力,可以自动在Chrome,Firefox,IE等主流浏览器依次跑完测试用例,同时也支持headless浏览器(入phantomJs)中运行测试用例。webpack+babel可以主动为想要适配的浏览器提供转码和垫片补丁引入能力,而Karma可以为最终的结果提供验证能力。Karma的配置方式可以阅读《webpack4.0各个击破(9)——Karma篇》进行了解。 -
Mocha
mocha是前端自动化测试框架,提供了不同风格断言库的兼容,测试用例分组,同步、异步测试架构,生命周期钩子等能力。基本用法
describe('我现在要测某一个页面的几个功能',function(){ describe('现在要测XX功能',function(){ it('某个变量的值应该是数字',function(){ //写断言 }) }); describe('现在要测YY功能',function(){ it('某个数组长度应该不小于10',function(){ //写断言 }) }); })
异步测试
describe('现在要测XX功能',function(){ // done 传入done函数,在测试结束的时候调用,告知测试程序结束了,否则程序会等待超时 it('异步请求应该返回一个对象', function(done){ request .get('https://api.github.com') .end(function(err, res){ expect(res).to.be.an('object'); done(); }); }); // promise的支持 it('异步请求应该返回一个对象', function() { return fetch('https://api.github.com') .then(function(res) { return res.json(); }).then(function(json) { expect(json).to.be.an('object'); }); }); // async await it('#async function', async () => { let r = await hello(); assert.strictEqual(r, 15); }); });
不同风格的断言库
支持should.js,expect.js及node核心断言模块assert等。
生命周期钩子
describe('hooks', function() { before(function() { // 在本区块的所有测试用例之前执行 }); after(function() { // 在本区块的所有测试用例之后执行 }); beforeEach(function() { // 在本区块的每个测试用例之前执行 }); afterEach(function() { // 在本区块的每个测试用例之后执行 }); // test cases });
生命周期钩子一般用来建立和清理环境或全局变量。
-
chai
Chai是一个断言库合集,支持expect,assert,should断言语法,非专业测试岗位其实没必要深究,了解使用方法就可以了。使用示例:expect(bar).to.not.exist;//断言变量bar不存在 expect(data).to.have.ownProperty('length');//断言data有length属性 expect(name).to.be.a('string');//断言name是一个字符串 assert.equal(value1,value2);//断言value1和value2相等 Tim.should.be.an.instanceof(Person);//断言Tim是Person类的实例
简单测试用例
// calculate.js
export default {
add (a, b) {
return a + b
},
minus (a, b) {
return a - b - 1
}
}
// calculate.spec.js
import calculate from '@/js/calculate'
describe('calculate test', () => {
it('function add should return right sum', () => {
expect(calculate.add(1, 2))
.equal(3)
})
it('function minus should return right value', () => {
expect(calculate.minus(2, 1)).equal(1)
})
})
运行测试
// package.json
{
"scripts": {
"unit": "karma start karma.conf.js"
}
}
===》 npm run unit
结果报告
与vue结合的测试
import Vue from 'vue' // 导入Vue用于生成Vue实例
import Hello from '@/components/Hello' // 导入组件
// 测试脚本里面应该包括一个或多个describe块,称为测试套件(test suite)
describe('Hello.vue', () => {
// 每个describe块应该包括一个或多个it块,称为测试用例(test case)
it('should render correct contents', () => {
const Constructor = Vue.extend(Hello) // 获得Hello组件实例
const vm = new Constructor().$mount() // 将组件挂在到DOM上
//断言:DOM中class为hello的元素中的h1元素的文本内容为Welcome to Your Vue.js App
expect(vm.$el.querySelector('.hello h1').textContent)
.to.equal('Welcome to Your Vue.js App')
})
})
karma.conf.js的配置
var webpackConfig = require('../../build/webpack.test.conf')
module.exports = function (config) {
config.set({
// 插件
plugins: [
'karma-chrome-launcher',
'karma-webpack',
'karma-mocha',
'karma-chai',
'karma-spec-reporter',
'karma-coverage'
],
// 浏览器
browsers: ['PhantomJS'],
// 测试框架
frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'],
// 测试报告
reporters: ['spec', 'coverage'],
// 测试入口文件
files: ['./index.js'],
// 预处理器 karma-webpack
preprocessors: {
'./index.js': ['webpack', 'sourcemap']
},
// Webpack配置
webpack: webpackConfig,
// Webpack中间件
webpackMiddleware: {
noInfo: true
},
// 测试覆盖率报告
// https://github.com/karma-runner/karma-coverage/blob/master/docs/configuration.md
coverageReporter: {
dir: './coverage',
reporters: [
{ type: 'lcov', subdir: '.' },
{ type: 'text-summary' }
]
}
})
}
util.js
从Vue官方的demo可以看出,对于Vue的单元测试我们需要将组件实例化为一个Vue实例,有时还需要挂载到DOM上,推荐Element的单元测试工具脚本Util.js,它封装了Vue单元测试中常用的方法
import Vue from 'vue'
import Element from 'element-ui'
Vue.use(Element)
let id = 0
const createElm = function () {
const elm = document.createElement('div')
elm.id = 'app' + ++id
document.body.appendChild(elm)
return elm
}
/**
* 回收 vm
* @param {Object} vm
*/
exports.destroyVM = function (vm) {
vm.$destroy && vm.$destroy()
vm.$el &&
vm.$el.parentNode &&
vm.$el.parentNode.removeChild(vm.$el)
}
/**
* 创建一个 Vue 的实例对象
* @param {Object|String} Compo 组件配置,可直接传 template
* @param {Boolean=false} mounted 是否添加到 DOM 上
* @return {Object} vm
*/
exports.createVue = function (Compo, mounted = false) {
if (Object.prototype.toString.call(Compo) === '[object String]') {
Compo = { template: Compo }
}
return new Vue(Compo).$mount(mounted === false ? null : createElm())
}
/**
* 创建一个测试组件实例
* @link http://vuejs.org/guide/unit-testing.html#Writing-Testable-Components
* @param {Object} Compo - 组件对象
* @param {Object} propsData - props 数据
* @param {Boolean=false} mounted - 是否添加到 DOM 上
* @return {Object} vm
*/
exports.createTest = function (Compo, propsData = {}, mounted = false) {
if (propsData === true || propsData === false) {
mounted = propsData
propsData = {}
}
const elm = createElm()
const Ctor = Vue.extend(Compo)
return new Ctor({ propsData }).$mount(mounted === false ? null : elm)
}
/**
* 触发一个事件
* mouseenter, mouseleave, mouseover, keyup, change, click 等
* @param {Element} elm
* @param {String} name
* @param {*} opts
*/
exports.triggerEvent = function (elm, name, ...opts) {
let eventName
if (/^mouse|click/.test(name)) {
eventName = 'MouseEvents'
} else if (/^key/.test(name)) {
eventName = 'KeyboardEvent'
} else {
eventName = 'HTMLEvents'
}
const evt = document.createEvent(eventName)
evt.initEvent(name, ...opts)
elm.dispatchEvent
? elm.dispatchEvent(evt)
: elm.fireEvent('on' + name, evt)
return elm
}
/**
* 触发 “mouseup” 和 “mousedown” 事件
* @param {Element} elm
* @param {*} opts
*/
exports.triggerClick = function (elm, ...opts) {
exports.triggerEvent(elm, 'mousedown', ...opts)
exports.triggerEvent(elm, 'mouseup', ...opts)
return elm
}
/**
* 触发 keydown 事件
* @param {Element} elm
* @param {keyCode} int
*/
exports.triggerKeyDown = function (el, keyCode) {
const evt = document.createEvent('Events')
evt.initEvent('keydown', true, true)
evt.keyCode = keyCode
el.dispatchEvent(evt)
}
使用的例子
// Hello.vue
<template>
<div class="hello">
<h1 class="hello-title">{{ msg }}</h1>
<h2 class="hello-content">{{ content }}</h2>
</div>
</template>
<script>
export default {
name: 'hello',
props: {
content: String
},
data () {
return {
msg: 'Welcome!'
}
}
}
</script>
// Hello.spec.js
import { destroyVM, createTest } from '../util'
import Hello from '@/components/Hello'
describe('Hello.vue', () => {
let vm
afterEach(() => {
destroyVM(vm)
})
it('测试获取元素内容', () => {
vm = createTest(Hello, { content: 'Hello World' }, true)
expect(vm.$el.querySelector('.hello h1').textContent).to.equal('Welcome!')
expect(vm.$el.querySelector('.hello h2').textContent).to.have.be.equal('Hello World')
})
it('测试获取Vue对象中数据', () => {
vm = createTest(Hello, { content: 'Hello World' }, true)
expect(vm.msg).to.equal('Welcome!')
// Chai的语言链是无意义的,可以随便写。如下:
expect(vm.content).which.have.to.be.that.equal('Hello World')
})
it('测试获取DOM中是否存在某个class', () => {
vm = createTest(Hello, { content: 'Hello World' }, true)
expect(vm.$el.classList.contains('hello')).to.be.true
const title = vm.$el.querySelector('.hello h1')
expect(title.classList.contains('hello-title')).to.be.true
const content = vm.$el.querySelector('.hello-content')
expect(content.classList.contains('hello-content')).to.be.true
})
})
测试结果:
Hello.vue
√ 测试获取元素内容
√ 测试获取Vue对象中数据
√ 测试获取DOM中是否存在某个class
测试注意:
1、 测试脚本都要放在 test/unit/specs/ 目录下
2、 脚本命名方式为 [组件名].spec.js
3、所谓断言,就是对组件做一些操作,并预言产生的结果。如果测试结果与断言相同则测试通过
4、单元测试默认测试 src 目录下除了 main.js 之外的所有文件,可在 test/unit/index.js 文件中修改
5、Chai断言库中,to be been is that which and has have with at of same 这些语言链是没有意义的,只是便于理解而已
参考博客:
https://blog.51cto.com/13869008/2175983
https://www.jianshu.com/p/34afa645487b
https://blog.csdn.net/qq_34179086/article/details/89496825