单元测试总结
BDD
Behavior Driven Development(行为驱动开发)
TDD
Test Driven Development(测试驱动开发)
- 先写错误情况(比如邮箱验证,先观察测试能否发现邮箱格式不正确的情况),再写正确情况
- 手动添加实例监听事件(listener),手动触发元素事件(trigger),观察回调函数是否执行(callback)
目录结构
|-- tests
|-- fixture
| |-- db.js
|-- unit
|-- xxx.test.spec.js
- tests/fixture: 用于存放测试数据。
- tests/unit: 用于存放测试用例。
测试对象
- 测DOM元素是否存在
- 测事件
- css不测
Karma
- 作用
提供测试环境(呼起浏览器,加载测试脚本,然后运行测试用例)
- 配置
具体配置写在 karma.conf.js 中
- JSDOM
与Karma作用类似,是在 Node 虚拟浏览器环境运行测试。也就是说它不呼起浏览器。
Mocha,chai,Sinon
只是提供测试语法
- Mocha
describe,it 语法
- chai
expect 语法
- Sinon/Sinon-Chai
为组件的事件测试设置回调函数
const callback = sinon.fake();
事件测试
- 为什么测试'点击 button 触发 touch 事件'不能用以下方式
vm.$on('touch', function(){
console.log('touch')
expect(1).to.eq(1)
})
vm.$el.click()
- 因为我们要用 expect 实现“监听touch事件”,不能用 console.log
- 但是 expect(1).to.eq(1) 这种写法不能让我们知道回调函数是否执行
所以我们需要的是:用 expect 描述"回调函数被执行了"这件事情
const callback = sinon.fake(); // 一但 callback 被调用,会再内存中留下标记
vm.$on('touch', callback)
vm.$el.click() // 注意如果测试正确,这里 callback 已经被调用了
expect(callback).to.have.been.called // 去问内存:callback 是否被调用了?
- listeners:添加在实例上,用于设置组件的update/add:selected之类事件的回调函数
it('会触发 update:selected 事件', (done) => {
Vue.component('g-nav-item', NavItem)
const callback = sinon.fake();
const wrapper = mount(Nav, {
propsData: {
selected: 'home'
},
slots: {
default: `
<g-nav-item name="developers">开发团队</g-nav-item>
`
},
listeners: {
'update:selected': callback
}
})
wrapper.find('[data-name="developers"]').trigger('click')
expect(callback).to.have.been.calledWith('developers')
done()
})
关于异步
由于DOM元素的渲染是异步的,所以我们往往需要在设置 PropsData 后异步地测试DOM元素情况
it('toast 接受 autoClose 属性', (done) => {
const wrapper = mount(Toast, {
propsData: {
autoClose: 1
},
})
// 确保在vm.execAutoClose执行后再去判断vm.$el是否被移出document.body
setTimeout(() => {
// 判断vm.$el是否被移出document.body
expect(document.body.contains(wrapper.element)).to.equal(false)
done()
}, wrapper.props('autoClose') * 1000)
})
data-xxx
对于 selected 属性的测试,常用 data-xxx 来标记DOM元素
<nav-item name="home"></nav-item>
// nav-item.vue
<template>
<div :data-name="name" :class="{selected}">
...
</div>
</template>
it('支持 selected 属性', (done) => {
Vue.component('g-nav-item', NavItem)
const wrapper = mount(Nav, {
propsData: {
selected: 'home'
},
...
}
})
setTimeout(() => {
expect(wrapper.find('[data-name="home"].selected').exists()).to.be.true
done()
})
})
优先级
比如邮箱验证时,required 的优先级高于 pattern
it('required & pattern', () => {
let data = {
email: ''
}
let rules = [
{key: 'email',pattern:'email',required:true}
]
let errors = validate(data, rules)
expect(errors.email.required).to.eq('必填')
expect(errors.email.pattern).to.not.exist // 只显示"必填"这一错误信息
})
而 pattern 与 minLength 同级
it('pattern & minLength', () => {
let data = {
email: '12'
}
let rules = [
{key: 'email', pattern: 'email', required: true, minLength: 6}
]
let errors = validate(data, rules)
expect(errors.email.pattern).to.eq('格式不正确')
expect(errors.email.minLength).to.eq('太短')
})