以前认为写测试代码的这个时间,还不如直接点点点测试来的快呢,而且那么复杂的逻辑,写测试代码也挺不容易的吧,其实关于复杂以及耗时间这两点,我现在有了些新的认知:
- 首先,复杂的逻辑写测试代码不容易这点,对于哪些业务、功能需要写测试代码,你要有清晰的认知,知道这点后,那么写代码的时候就要注意了,多个地方用到的尽量提取成公用的代码,逻辑复杂的要尽量往细了拆分,而且通过测试代码也能看出,测试的时候关注的也仅仅是输入输出,中间经历了哪些弯弯折折的,这些都不关心的,那么自己写业务、功能代码的时候就会刻意去注意了,输入输出是什么,如果写的代码中间耦合了很多别的东西,测试代码没办法去写,那你就需要考虑以这种方式写代码是不是不合适,看看有没有更优的写法。
- 其次,对于写测试代码耗时间,不如点点点测试来的快这个问题,我是这么认为的,或许你们的项目太小了,或许是你的认知水平还没到这一层级,举个例子:有的公司跑测试代码需要连续不停的跑8个小时才能将所有的测试代码跑完,如果换人工去做,那消耗的时间就不可估量了,而且人工去挨个测是非常容易出问题的。对于写测试代码这件事,可能前期会有点麻烦,但是对于以后真是省时省力,造福人类~
测试分类
1. 黑盒测试
常见的开发流程里,都有测试人员,他们不管内部实现机制,只看最外层的输入输出,这种我们称为黑盒测试。比如:你写一个加法的页面,会设计N个用例,测试加法的正确性,这种测试我们称之为E2E测试
2. 白盒测试
还有一种测试叫做白盒测试,我们针对一些内部核心实现逻辑写测试代码,称之为单元测试。
3. 集成测试
集成测试就是集合多个测试过的单元一起测试。
编写测试代码的好处
- 提供描述组件行为的文档
- 节省手动测试的时间
- 减少研发新特性时产生的bug
- 改进设计
- 促进重构
自动化测试使得大团队中的开发者可以维护复杂的基础代码。让你改代码不再小心翼翼
准备工作
使用工具
在vue中国,推荐使用Mocha+Chai或者Jest
1.要完成测试任务,需要测试框架(跑测试),断言库(编写测试)和编程框架特有的测试套件。
2.上面的Mocha是测试框架,Chai是断言库,Jest同时包含两种。
3.vue中组件等测试代码的编写需要vue-test-utils套件支持。
新建vue项目/已存项目中集成
-
新建vue项目
- 选择Unit Testing和E2E Testing

- 单元测试解决方案选择:Jest

- 端到端测试解决方案选择:Cypress

-
已存项目中集成
运行
vue add @vue/unit-jest和vue add @vue/e2e-cypress注意:安装的时候对node版本是有要求的,需要10.0以上的版本
单元测试
单元测试(unit testing):是对软件中的最小可测试单元进行检查和验证。
编写
安装完@vue/unit-jest后,项目中会有tests/unit目录,在下面创建测试文件,命名规范:必须是*.spec.js的文件名
// test/unit/test.spec.js
function add (num1, num2) {
return num1 + num2
}
// 测试套件 test suite
describe('test', () => {
// 测试用例 test case
it('测试add函数', () => {
// 断言 assert
expect(add(1, 3)).toBe(3)
expect(add(1, 3)).toBe(4)
expect(add(-2, 3)).toBe(1)
})
})
断言API介绍
1.describe:定义一个测试套件
2.it:定义一个测试用例
3.expect:断言的判断条件
更多断言断言API
执行
npm run test:unit

测试vue组件
vue官方提供了用于单元测试的使用工具库@vue/test-utils
- 创建组件
// src/components/testUnit/index.vue
<template>
<div>
<span>{{ message }}</span>
<button @click="changeMsg">点击</button> </div>
</template>
<script>
export default {
data () {
return {
message: 'vue-text'
}
},
created () {
this.message = '更改message'
},
methods: {
changeMsg () {
this.message = '按钮点击'
}
}
}
</script>
- 测试组件
import testUnit from '@/components/testUnit/'
describe('testUnit.vue', () => {
// 检查组件选项
it('要求设置created生命周期', () => {
expect(typeof testUnit.created).toBe('function')
})
it('message初始值是vue-test', () => {
// 检查data函数存在性
expect(typeof testUnit.data).toBe('function')
// 检查data返回的默认值
const defaultData = testUnit.data()
expect(defaultData.message).toBe('vue-test')
})
})

- 检查mounted之后的预期结果
<template>
<div>
<span>{{ message }}</span>
<button @click="changeMsg">点击</button> </div>
</template>
<script>
export default {
data () {
return {
message: 'vue-test'
}
},
mounted () {
this.message = '修改后的message'
},
methods: {
changeMsg () {
this.message = '按钮点击'
}
}
}
</script>
import { shallowMount } from '@vue/test-utils'
import testUnit from '@/components/testUnit/'
describe('testUnit.vue', () => {
// 检查组件选项
it('要求设置created生命周期', () => {
expect(typeof testUnit.created).toBe('function')
})
it('message初始值是vue-test', () => {
// 检查data函数存在性
expect(typeof testUnit.data).toBe('function')
// 检查data返回的默认值
const defaultData = testUnit.data()
expect(defaultData.message).toBe('vue-test')
})
it('mount之后data中message数据是修改后的message', () => {
const wrapper = shallowMount(testUnit)
// 你可以通过 `wrapper.vm` 访问实际的 Vue 实例
expect(wrapper.vm.message).toBe('hhhh')
})
})

- 点击按钮后测试数据变化
<template>
<div>
<span>{{ message }}</span>
<button @click="changeMsg">点击</button>
</div>
</template>
<script>
export default {
data () {
return {
message: 'vue-test'
}
},
methods: {
changeMsg () {
this.message = '按钮点击'
}
}
}
</script>
import { shallowMount } from '@vue/test-utils'
import testUnit from '@/components/testUnit/'
describe('testUnit.vue', () => {
it('按钮点击后', async () => {
const wrapper = shallowMount(testUnit)
// 模拟用户交互
await wrapper.find('button').trigger('click')
// 测试数据变化
expect(wrapper.vm.message).toBe('按钮点击')
// 测试html渲染结果
expect(wrapper.find('span').html()).toBe('<span>按钮点击</span>')
// 等效的方式
expect(wrapper.find('span').text()).toBe('按钮点击')
})
})
注意:任何导致操作 DOM 的改变都应该在断言之前 await nextTick 函数。因为 Vue 会对未生效的 DOM 进行批量异步更新,避免因数据反复变化而导致不必要的渲染。
端到端测试
借用浏览器的能力,站在用户测试人员的角度,输入框、点击按钮等,完全模拟用户,这个和具体的框架关系不大,完全模拟浏览器行为。
测试文件
// tests/e2e/spec/test.js
// https://docs.cypress.io/api/introduction/api.html
describe('My First Test', () => {
it('Visits the app root url', () => {
cy.visit('/')
cy.contains('h1', 'Welcome to Your Vue.js App')
})
})
运行
npm run test:e2e

修改App.vue文件
<template>
<div id="app">
<h1>Welcome to Your Vue.js App</h1>
<router-view />
</div>
</template>

测试覆盖率
jest自带覆盖率,如果用的mocha,需要使用Istanbul来统计覆盖率。
package.json里修改命令配置:
script:{
"test": "jest --coverage"
}
package.json里修改jest配置:
"jest": {
"collectCoverage": true,
"collectCoverageFrom": ["src/**/*.{js,vue}"],
}
也可以独立配置,在根目录下创建jest.config.js
module.exports = {
"collectCoverage": true,
"collectCoverageFrom": ["src/**/*.{js,vue}"]
}
执行:
npm run test

%stmts是语句覆盖率(statement coverage):是不是每个语句都执行了?
%Branch分支覆盖率(branch coverage):是不是每个if代码块都执行了?
%Funcs函数覆盖率(function coverage):是不是每个函数都调用了?
%Lines行覆盖率(line coverage):是不是每一行都执行了?