当前时代,前端技术的飞速发展与更迭,前端框架的百花齐放,前端从业人员激增,作为一个有素养,有理想的前端童鞋如何保证自己的代码质量,让自己的代码更加严谨、可靠成了我们绕不过去的话题,这时候就离不开测试,当然作为一个开发人员的话我们更注重的应该是单元测试。
ATDD,TDD,BDD
聊到了测试,那我们先简单提几个概念:ATDD,TDD,BDD.
ATDD: Acceptance Test Driven Development(验收测试驱动开发):
这是一种在编码开始之前将客户带入测试设计过程的技术。它也是一个协作实践,用户,测试人员和开发人员定义了自动验收标准。如果系统未通过测试可提供快速反馈,说明未满足要求。验收测试以业务领域术语进行指定。每个功能都必须提供真实且可衡量的业务价值。
TDD: Test-driven development (测试驱动开发):
是一种使用自动化单元测试来推动软件设计并强制依赖关系解耦的技术。在编写真正实现功能的代码之前先编写测试,每次测试之后,重构完成,然后再次执行相同或类似的测试。该过程根据需要重复多次,直到每个单元根据所需的规格运行。
BDD:Behavior-Driven Development (行为驱动开发):
是一种敏捷软件开发的技术,它鼓励软件项目中的开发者、QA和非技术人员或商业参与者之间的协作。
使用BDD的团队应该能够以用户故事的形式提供大量的“功能文档”,并增加可执行场景或示例。 BDD通常有助于领域专家理解实现而不是暴露代码级别测试。它通常以GWT格式定义:GIVEN WHEN&THEN。
测试工具的类型
组合使用工具很常见,即使已选框架也能实现类似的功能
- 提供测试框架(Mocha, Jasmine, Jest, Cucumber)
- 提供断言(Chai, Jasmine, Jest, Unexpected)
- 生成,展示测试结果(Mocha, Jasmine, Jest, Karma)
- 快照测试(Jest, Ava)
- 提供仿真(Sinon, Jasmine, enzyme, Jest, testdouble)
- 生成测试覆盖率报告(Istanbul, Jest, Blanket)
- 提供类浏览器环境(Protractor, Nightwatch, Phantom, Casper)
不同测试框架的对比
Jest
- facebook 坐庄
- 基于 Jasmine 至今已经做了大量修改添加了很多特性
- 开箱即用配置少,API简单
- 支持断言和仿真
- 支持快照测试
- 在隔离环境下测试
- 互动模式选择要测试的模块
- 优雅的测试覆盖率报告,基于Istanbul
- 智能并行测试
- 较新,社区不十分成熟
- 全局环境,比如 describe 不需要引入直接用
- 较多用于 React 项目(但广泛支持各种项目)
Mocha
- 灵活(不包括断言和仿真,自己选对应工具) 流行的选择:chai,sinon
- 社区成熟用的人多,测试各种东西社区都有示例
- 需要较多配置
- 可以使用快照测试,但依然需要额外配置
Jasmine
- 开箱即用(支持断言和仿真)
- 全局环境
- 比较'老',坑基本都有人踩过了
AVA
- 异步,性能好
- 简约,清晰
- 快照测试和断言需要三方支持
Tape
- 体积最小,只提供最关键的东西
- 对比其他框架,只提供最底层的 API
总结一下,Mocha 用的人最多,社区最成熟,灵活,可配置性强易拓展,Jest 开箱即用,里边啥都有提供全面的方案,Tape 最精简,提供最基础的东西最底层的API。
下面开始今天主要的话题,前端单元测试jest.js的学习使用
Jest 作为 Facebook 的一套开源的 JavaScript 测试框架, 它自动集成了断言、JSDom、覆盖率报告等开发者所需要的所有测试工具,是一款几乎零配置的测试框架。
单元测试主要思想与步骤
-
编写测试用例
即描述单元测试要做什么,给定一个输入,定义期望的输出,检验要测试的代码是否符合期望
-
编写被测试代码
-
执行测试,如果未通过测试,则修改被测试代码直到测试通过
初始化与安装
// 新建个项目并初始化
mkdir study-jest && cd
npm init -y
// 下载安装jest
npm i jest -D
// 配置script脚本,以便通过命令行运行测试
"scripts": {
"jest-t": "jest"
}
npm install -D babel-jest babel-core babel-preset-env regenerator-runtime
/* babel-jest、 babel-core、 regenerator-runtime、babel-preset-env这几个依赖是为了让我们可以
使用ES6的语法特性进行单元测试,ES6提供的 import 来导入模块的方式,Jest本身是不支持的。*/
// 配置.babelrc文件
{
"presets": ["env"]
}
first blood
默认情况下,我们的测试文件一般放在项目中的tests文件夹中,并且我们测试文件命名一般是xx.spec.js,所以创建一个tests文件夹,并在里边创建一个名为firstTest.spec.js的文件来编写我们的第一个测试用例
现在我们要实现一个sum函数用来做整数的相加,首先我们来编写他的测试用例
// firstTest.spec.js
// 描述要测试的模块
test('sum(1 + 1) 等于 2', () => {
// 断言:即期望运行结果
expect(functions.sum(1, 1)).toBe(2);
});
编写被测试代码
// functions.js
export default {
sum(a, b) {
// return a; // 错误逻辑
return a + b;
}
}
编写完成之后执行测试指令npm run jest-t
测试成功输出
测试失败输出
常用的几个Jest断言
上面测试用例中的expect(functions.sum(2, 2)).toBe(4)为一句断言,Jest为我们提供了expect函数用来包装被测试的方法并返回一个对象,该对象中包含一系列的匹配器来让我们更方便的进行断言,上面的toBe函数即为一个匹配器。我们来介绍几种常用的Jest断言,其中会涉及多个匹配器。
.not
// firstTest.spec.js
// .not修饰符允许你测试结果不等于某个值的情况
test('sum(1 + 1) 不等于 3', () => {
expect(functions.sum(1, 1)).not.toBe(3);
});
.toEqual()
// functions.js
export default {
getObj() {
return {
a: 1,
b: 2
};
}
};
// firstTest.spec.js
/* .toEqual匹配器会递归的检查对象所有属性和属性值是否相等,所以如果要进行应用类型的比较
时,请使用.toEqual而不是.toBe */
test('getObj() 返回的对象深度相等', () => {
expect(functions.getObj()).toEqual(functions.getObj());
});
test('getObj()返回的对象内存地址不同', () => {
expect(functions.getObj()).not.toBe(functions.getObj());
});
.toHaveLength
// functions.js
export default {
getIntArray(num) {
if (!Number.isInteger(num)) {
throw Error('"getIntArray"只接受整数类型的参数');
}
let result = [];
for (let i = 0, len = num; i < len; i++) {
result.push(i);
}
return result;
};
}
// firstTest.spec.js
// .toHaveLength可以很方便的用来测试字符串和数组类型的长度是否满足预期。
test('getIntArray(3)返回的数组长度应该为3', () => {
expect(functions.getIntArray(3)).toHaveLength(3);
});
.toThrow
// firstTest.spec.js
/* .toThrow可能够让我们测试被测试方法是否按照预期抛出异常,但是在使用时需要注意的是:我们
必须使用一个函数将将被测试的函数做一个包装,正如上面getIntArrayWrapFn所做的那样,否则会
因为函数抛出导致该断言失败。*/
test('getIntArray(3.3)应该抛出错误', () => {
function getIntArrayWrapFn() {
functions.getIntArray(3.3);
}
expect(getIntArrayWrapFn).toThrow('"getIntArray"只接受整数类型的参数');
});
.toMatch
// firstTest.spec.js
// .toMatch传入一个正则表达式,它允许我们用来进行字符串类型的正则匹配。
test('getObj().a应该为数字', () => {
expect(functions.getObj().a).toMatch(/\d/i);
});
double kill
异步函数测试
这里我们使用最常用的http请求库axios来进行请求处理
npm install axios
编写http请求函数
我们将请求http://jsonplaceholder.typicode.com/users/1,这是由JSONPlaceholder提供的mock请求地址
// asyncFn.js
import axios from 'axios';
export default {
fetchUser() {
return axios.get('http://jsonplaceholder.typicode.com/users/1')
.then(res => res.data)
.catch(error => console.log(error));
}
}
// secondTest.js
test('fetchUser() 可以请求到一个含有name属性值为Leanne Graham的对象', () => {
expect.assertions(1);
return asyncFn.fetchUser()
.then(data => {
expect(data.name).toBe('Leanne Graham');
});
});
/* 上面我们调用了expect.assertions(1),它能确保在异步的测试用例中,有一个断言会在回调函数中
被执行。这在进行异步代码的测试中十分有效。*/
代码覆盖率
代码覆盖率指的是我们写的代码有没有单元测试所覆盖到。Jest 具有内置的代码覆盖范围,你可以通过两种方式激活它:
- 通过命令行传递标志
--coverage - 在
package.json中配置Jest
// 方式一:命令行
npm run jest-t -- --coverage
// 方式二:配置package.json中的jest
"jest": {
"collectCoverage": true
}
// 方式三:配置package.json中的script指令
"scripts": {
"jest-t": "jest --coverage"
},
测试完毕报告
我们每次运行完npm run jest-t时可以在项目中看到一个名为coverage的文件夹,你可以通过访问coverage/lcov-report/index.html来查看详细的测试报告他可以明确的指出未经测试的代码。
详细报告
作者:mhfe123
参考资料
【1】 Jest官方文档(jestjs.io/zh-Hans/)
【2】李棠辉-简书(www.jianshu.com/p/70a4f026a…)