1. 前端自动化测试框架
完整的自动化测试框架应包含以下 3 个部分:
- 断言库(Assertion Library):用于比对 "预期输出" 和 "实际输出",并抛出错误;
- 模拟库(Mock Library):用于模拟数据对象、网络接口等;
- 测试运行器(Test Runner):用于运行测试代码;
一、常用前端单元测试框架:
- Jest(完整测试框架)
- 一个由 Facebook 开源的测试框架,比较新而且目前也比较火
- 功能全面
- 零配置
- Mocha(核心测试框架)
二、常用的e2e测试框架:
2. 使用Jest编写单元测试
使用 Jest 框架来编写单元测试的基本用法
核心内容:
jest 的基本用法,Jest会自动识别和执行项目中所有以.test.js
为后缀的测试代码文件。
具体步骤:
- 新建项目并安装 jest
bash
npm i jest --save-dev
- 编写一些功能代码(先使用 CommonJS 模块,因为用 ESM 需要借助 Babel)
src/util.js
js
function toUpperCase(str) {
return str ? str.toUpperCase() : ""
}
module.exports = {
toUpperCase
}
- 编写用于测试以上代码的 jest 测试代码
jest 提供的常用测试函数:
test()
:创建测试用例,代表一个要测试的场景expect()
:创建匹配器,并利用.toBe()
、.toEqual()
等方法进行数据比对
src/util.test.js
js
// 导入要测试的模块
const { toUpperCase } = require('./util')
// 测试用例1:传入参数 'hello',返回 'HELLO'
test('传入hello返回HELLO', () => {
expect(toUpperCase('hello')).toBe('HELLO')
})
// 测试用例2:传入参数 null,返回 ''
test('传入空值返回空字符串', () => {
expect(toUpperCase(null)).toBe('')
})
- 执行测试
bash
npx jest
运行结果:
如果目标代码使用了 ESM 模块化,那测试代码也需用 ESM。这时需要安装配置 Babel 进行支持:
- 安装 babel
sql
npm i babel-jest @babel/core @babel/preset-env --save-dev
- 创建
babel.config.js
js
module.exports = {
presets: [
['@babel/preset-env', {
targets: {
node: 'current'
}
}]
],
};
- 在测试代码中使用
import
导入目标测试代码
js
import { toUpperCase } from './util'
3. 统计测试覆盖率
了解测试覆盖率的含义和功能
测试覆盖率,也叫代码覆盖率,指的是被测试的目标代码中至少被执行了一次的条目数占所有条目数的百分比。 它是衡量测试的充分性和完整性的重要指标
。
覆盖率根据统计条目的类型,细分为:
语句覆盖率
:以代码语句为最小单位;路径覆盖率
:以逻辑路径为最小单位;函数覆盖率
:以函数为最小单位;代码行覆盖率
:以代码行为最小单位;
统计代码覆盖率的目的:
- 找出遗漏的测试场景
- 找出没用到的代码
常用工具
- Jest - 自带了覆盖率统计工具
- Karma - 借助第三方库 Istanbul
具体步骤
- 在上一章节代码中,执行以下命令生成覆盖率报告
bash
npx jest --coverage
运行结果:
另外会生成一个包含 html 格式覆盖率报告的 coverage
目录。
4. 使用Jest测试异步代码
测试包含有如网络请求、定时器等异步行为的目标代码
核心内容:
方式一:回调风格
通过 test()
的回调函数参数 done (它也是个函数,调用它即可告知完成异步任务)
js
test('some desc', done => {
someAsyncTask(data => {
try {
expect(data).toBe('....')
done()
} catch(e) {
done(e)
}
})
})
方式二:Promise/async + await 风格
通过在 test()
的回调函数中返回一个 promise 对象
js
test('some desc', () => {
return someAsyncTask().then(data => {
expect(data).toBe('...')
})
})
或直接用 async/await
js
test('some desc', async () => {
const data = await someAsyncTask()
expect(data).toBe('...')
})
具体步骤:
src/request.js
js
// 发送请求的异步逻辑
export function getRemoteData() {
return new Promise((reslove, reject) => {
const data = {
status: 200,
list: [
{ id: 1, label: '三国', },
{ id: 2, label: '水浒传' },
{ id: 3, label: '红楼梦' },
{ id: 4, label: '西游记' }
]
}
reslove(data);
})
}
- 在
src/request.test.js
测试代码中添加新的测试用例:
js
import { getRemoteData } from './request.js'
// 测试用例3:获取异步数据
test('get remote data', async () => {
// 测试返回 promise 的异步代码,必须要返回 promise 对象
// 这样才能让测试正确结束
const data = await getRemoteData()
expect(data.status).toBe(200)
})
5.使用Cypress进行e2e测试
安装和使用 Cypress 进行端到端测试
核心内容:
- 安装和启动 Cypress
- 编写和运行 Cypress 测试代码
具体步骤:
- 安装 Cypress
bash
npm i cypress --save-dev
- 打开 Cypress
bash
npx cypress open
Cypress 会搜索到用户电脑上的所有浏览器,供测试时选用:
-
创建e2e测试 测试文件名请按
xxx.cy.js
这种命名规则。 -
编写测试代码
e2e测试的本质其实就是模拟用户操作,即用户是如何使用网页界面的。所以 e2e 测试代码就是在利用各种 API 操作网页而已。
Cypress 使用了 Mocha + Chai,因此编写测试代码时用的是来自这两个库的函数,如: describe
、it
、expect
等。
js
/// <reference types="cypress" />
describe('测试百度搜索功能', () => {
// 每个测试用例都会执行的前置行为
beforeEach(() => {
// 打开百度首页
cy.visit('https://www.baidu.com')
})
// 一个测试用例
it('应该得到搜索结果', () => {
// 获取百度首页的关键字输入框,并输入关键字
cy.get('#kw').type('前端e2e测试')
// 获取百度首页的搜索按钮,并点击
cy.get('#su').click()
// 对比结果
cy.get('.nums_text').should('have.text', '百度为您找到相关结果约3,190,000个')
})
})
- 点击用例,运行测试
6.测试 Vue 项目
介绍针对 Vue 开发的应用代码进行自动化测试的工具
核心内容:
- 针对 Vue 代码的测试工具介绍
- 在 Vue 代码中添加测试工具,并编写测试案例
Vue的代码测试涉会及到以下三类:
- 单元测试(测试函数)- 可使用 Jest 或 Mocha,官方提供了相关插件,可查看官方文档
- 组件测试(测试Vue组件)- 官方推荐使用 Vue Test Utils 或它的进一步封装库 Vue Testing Library
- e2e测试(测试界面)- 可使用专为 Vue 封装的 Cypress
具体步骤:
一、测试 Vue 组件
- 在现有的 Vue CLI 项目中,通过 vue-cli 提供的命令添加 Vue Testing Library
bash
# 单元测试
vue add @vue/unit-jest
# 端到端测试
vue add @vue/e2e-cypress
执行后项目中会新增测试专用目录 tests
:
- 编写一个新组件
Counter.vue
,并在App.vue
中调用
js
// Counter.vue
<template>
<div>
<h1 class="title">
当前计数:
<span class="count">{{ count }}</span>
</h1>
<div>
<button class="btn decrease" @click="decreaseHandler">减少</button>
<button class="btn increase" @click="increaseHandler">增加</button>
</div>
</div>
</template>
<script>
export default {
props: {
init: {
type: Number,
default: 0
}
},
data() {
return {
count: this.init
}
},
methods: {
increaseHandler() {
this.count += 1
},
decreaseHandler() {
this.count -= 1
}
}
}
</script>
<style>
.count {
color: red;
}
</style>
App.vue
中的调用:
ruby
<Counter :init="10"/>
- 针对
Counter.vue
组件,编写以下几个单元测试
tests/unit/count.spec.js
js
import Counter from '@/components/Counter.vue'
import { mount } from '@vue/test-utils'
describe('Counter.vue', () => {
it('不传 init 属性时显示的初始计数是 0', () => {
// 渲染组件
const wrapper = mount(Counter)
// 获取组件内的数据
expect(wrapper.vm.count).toBe(0)
})
it('传入 init 属性值为 10 时显示的初始计数是 10', () => {
const wrapper = mount(Counter, {
// 为组件传入属性
propsData: {
init: 10
}
})
expect(wrapper.vm.count).toBe(10)
})
it('点击 3 下增加按钮及 1 下减少按钮,当前计数增加 2', () => {
const wrapper = mount(Counter)
// 获取组件内的元素
const btnIncrease = wrapper.find('.increase')
const btnDecrease = wrapper.find('.decrease')
// 模拟用户点击
btnIncrease.trigger('click')
btnIncrease.trigger('click')
btnIncrease.trigger('click')
btnDecrease.trigger('click')
// 比对数据
expect(wrapper.vm.count).toBe(2)
})
})
- 运行单元测试
bash
npm run test:unit
一共执行了两个单元测试文件,共执行了4条测试用例。
在以上单元测试中进行数据比对时,都从组件的实例(vm 对象)上获取数据来进行比对,但有时我们想测试的是界面是否已渲染成我们期望的内容,这时应该这样写:
js
it('点击 3 下增加按钮及 1 下减少按钮,当前计数增加 2', async () => {
const wrapper = mount(Counter)
// 获取组件内的元素
const btnIncrease = wrapper.find('.increase')
const btnDecrease = wrapper.find('.decrease')
// (修改点1)模拟用户点击(使用 await)
await btnIncrease.trigger('click')
await btnIncrease.trigger('click')
await btnIncrease.trigger('click')
await btnDecrease.trigger('click')
// (修改点2)获取界面上的内容来进行数据比较
expect(wrapper.get('.count').text()).toBe('2')
})
说明:此处使用 await
是因为 click
事件是异步的,这样才能在异步行为发生后正确获取重新渲染后的界面内容。
二、e2e 测试
在 vue 中进行 e2e 测试其实和直接使用 Cypress
没有太大差别。
- 通过以下命令启动 Cypress
bash
npm run test:e2e
- 和之前一样,编写和运行 Cypress 测试脚本即可