在使用vue-cli创建项目的时候,会提示要不要安装单元测试和e2e测试。既然官方推荐我们使用这两个测试框架,那么我们就动手去学习实践一下他们吧。
简介
Karma
Karma是一个基于Node.js的JavaScript测试执行过程管理工具(Test Runner)。该工具在Vue中的主要作用是将项目运行在各种主流Web浏览器进行测试。
换句话说,它是一个测试工具,能让你的代码在浏览器环境下测试。需要它的原因在于,你的代码可能是设计在浏览器端执行的,在node环境下测试可能有些bug暴露不出来;另外,浏览器有兼容问题,karma提供了手段让你的代码自动在多个浏览器(chrome,firefox,ie等)环境下运行。如果你的代码只会运行在node端,那么你不需要用karma。
Mocha
Mocha是一个测试框架,在vue-cli中配合chai断言库实现单元测试。
而Chai断言库可以看,很简单,多查多用就能很快掌握。
我对测试框架的理解
npm run unit 执行过程
- 执行 npm run unit 命令
- 开启Karma运行环境
- 使用Mocha去逐个测试用Chai断言写的测试用例
- 在终端显示测试结果
- 如果测试成功,karma-coverage 会在 ./test/unit/coverage 文件夹中生成测试覆盖率结果的网页。
Karma
对于Karma,我只是了解了一下它的。
下面是Vue的karma配置,简单注释了下:
config.set({
// 浏览器
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' }
]
}
})
}
Mocha和chai
我们看下官方的例子(都用注释来解释代码意思了):
需要知道的知识点:
- 测试脚本都要放在 test/unit/specs/ 目录下。
- 脚本命名方式为 [组件名].spec.js。
- 所谓断言,就是对组件做一些操作,并预言产生的结果。如果测试结果与断言相同则测试通过。
- 单元测试默认测试 src 目录下除了 main.js 之外的所有文件,可在 test/unit/index.js 文件中修改。
- Chai断言库中,to be been is that which and has have with at of same 这些语言链是没有意义的,只是便于理解而已。
- 测试脚本由多个 descibe 组成,每个 describe 由多个 it 组成。
- 了解异步测试
了解一下 describe 的钩子(生命周期)
// 在本区块的所有测试用例之前执行
});
after(function() {
// 在本区块的所有测试用例之后执行
});
beforeEach(function() {
// 在本区块的每个测试用例之前执行
});
afterEach(function() {
// 在本区块的每个测试用例之后执行
});
// test cases
});
实践
上面简单介绍了单元测试的用法,下面来动手在Vue中进行单元测试!
util.js
从Vue官方的demo可以看出,对于Vue的单元测试我们需要将组件实例化为一个Vue实例,有时还需要挂载到DOM上。
以上写法只是简单的获取组件,有时候我们需要传递props属性、自定义方法等,还有可能我们需要用到第三方UI框架。所以以上写法非常麻烦。
这里推荐Element的,它封装了Vue单元测试中常用的方法。下面demo也是根据该 Util.js来写的。 这里简单注释了下各方法的用途。
- 创建一个 Vue 的实例对象
- @param {Object|String} Compo - 组件配置,可直接传 template
- @param {Boolean=false} mounted - 是否添加到 DOM 上
- @return {Object} vm
*/
exports.createVue = function (Compo,mounted = false) {}
/**
- 创建一个测试组件实例
- @param {Object} Compo - 组件对象
- @param {Object} propsData - props 数据
- @param {Boolean=false} mounted - 是否添加到 DOM 上
- @return {Object} vm
*/
exports.createTest = function (Compo,propsData = {},mounted = false) {}
/**
- 触发一个事件
- 注: 一般在触发事件后使用 vm.$nextTick 方法确定事件触发完成。
- mouseenter,mouseleave,mouseover,keyup,change,click 等
- @param {Element} elm - 元素
- @param {String} name - 事件名称
- @param {} opts - 配置项
/
exports.triggerEvent = function (elm,name,...opts) {}
/**
- 触发 “mouseup” 和 “mousedown” 事件,既触发点击事件。
- @param {Element} elm - 元素
- @param {} opts - 配置选项
/
exports.triggerClick = function (elm,...opts) {}
示例一
示例一中我们测试了 Hello 组件的各种元素的数据,学习 util.js 的 destroyVM 和 createTest 方法的用法以及如何获取目标元素进行测试。获取DOM元素的方式可查看。
Hello.vue
Hello.spec.js
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对象中数据',true)
expect(vm.msg).to.equal('Welcome!')
// Chai的语言链是无意义的,可以随便写。如下:
expect(vm.content).which.have.to.be.that.equal('Hello World')
})
it('测试获取DOM中是否存在某个class',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
})
})
输出结果
示例二
示例二中我们使用 createTest 创建测试组件测试点击事件,用 createVue 创建Vue示例对象测试组件 Click 的使用。这里主要可以看下到 createVue 方法的使用。
Click.vue
Click.spec.js
let vm
afterEach(() => {
destroyVM(vm)
})
it('测试按钮点击事件',() => {
vm = createTest(Click,{
AddNum: 10,InitNum: 11
},true)
let buttonElm = vm.$el.querySelector('button')
buttonElm.click()
buttonElm.click()
buttonElm.click()
// setTimeout 的原因
// 在数据改变之后,界面的变化会有一定延时。不用timeout有时候会发现界面没有变化
setTimeout(done => {
expect(vm.ResultNum).to.equal(41)
expect(vm.$el.querySelector('.init-num').textContent).to.equal('初始值为11')
expect(vm.$el.querySelector('.click-num').textContent).to.equal('点击了3次')
expect(vm.$el.querySelector('.result-num').textContent).to.equal('最终结果为41')
done()
},100)
})
it('测试创建Vue对象',() => {
let result
vm = createVue({
template: <click @click="handleClick"></click>
,props: {
AddNum: 10,InitNum: 11
},methods: {
handleClick (obj) {
result = obj
}
},components: {
Click
}
},true)
vm.$el.click()
vm.$nextTick(done => {
expect(result).to.be.exist
expect(result.ClickNum).to.equal(1)
expect(result.ResultNum).to.be.equal(21)
done()
})
})
输出结果
click.vue √ 测试按钮点击事件 √ 测试创建Vue对象
其他
所有示例代码都放中便于查看。如果想查看更多好的测试用例,建议配合 Util.js 看一下 ,里面有很多测试脚本可以供我们学习。作为被广大Vue用户使用的UI组件库,测试脚本肯定也写很很不错的~甚至可以将这些脚本照抄一遍,相信这会对学习Vue组件的单元测试有很大帮助。
下面是本人看Element单元测试的笔记,供参考。
Util.js 方法包含了大多数Vue组件化的测试需求。
vm.$el vm.$nextTick 和 vm.$ref 都是在测试过程中比较常用的一些Vue语法糖。
需要注意: vm.$nextTick 方法是异步的,所以需要在里面使用done方法。
异步断言,方法参数需要是 _ 或者 done
大多数时候查询元素通过 querySelector 方法查询class获得
大多数情况下查询是否存在某个Class通过 classList.contains 方法获得,查找的结果为 true 或 false
异步测试必须以 done() 方法结尾。setTimeout 和 vm.$nextTick 是常用的异步测试。
实现按钮点击:通过获取按钮元素 btn,执行 btn.click() 方法实现。
由于 Vue 进行的情况,一些依赖DOM更新结果的断言必须在 Vue.nextTick 回调中进行。