前端测试工具一览
前端测试工具也和前端的框架一样纷繁复杂,其中常见的测试工具,大致可分为测试框架、断言库、测试覆盖率工具等几类。在正式开始本文之前,我们先来大致了解下它们:
测试框架
测试框架的作用是提供一些方便的语法来描述测试用例,以及对用例进行分组。
测试框架可分为两种: TDD (测试驱动开发)和 BDD (行为驱动开发),我理解两者间的区别主要是一些语法上的不同,其中 BDD 提供了提供了可读性更好的用例语法,至于详细的区别可参见
The Difference Between TDD and BDD
一文。
常见的测试框架有
Jasmine
,
Mocha
及
本文要介绍的
Jest
。
断言库
断言库主要提供语义化方法,用于对参与测试的值做各种各样的判断。这些语义化方法会返回测试的结果,要么成功、要么失败。常见的断言库有
Should.js
,
Chai.js
等。
测试覆盖率工具
用于统计测试用例对代码的测试情况,生成相应的报表,比如
istanbul
。
Jest
为什么选择 Jest
Jest 是 Facebook 出品的一个测试框架,相对其他测试框架,其一大特点就是就是内置了常用的测试工具,比如自带断言、测试覆盖率工具,实现了开箱即用。
而作为一个面向前端的测试框架, Jest 可以利用其特有的
快照测试
功能,通过比对 UI 代码生成的快照文件,实现对 React 等常见框架的自动测试。
此外, Jest 的测试用例是并行执行的,而且只执行发生改变的文件所对应的测试,提升了测试速度。目前在 Github 上其 star 数已经破万;而除了 Facebook 外,业内其他公司也开始从其它测试框架转向 Jest ,比如
Airbnb 的尝试
,相信未来 Jest 的发展趋势仍会比较迅猛。
安装
Jest 可以通过 npm 或 yarn 进行安装。以 npm 为例,既可用npm install -g jest进行全局安装;也可以只局部安装、并在 package.json 中指定 test 脚本:
{
"scripts": {
"test": "jest"
}
}
Jest 的测试脚本名形如*.test.js,不论 Jest 是全局运行还是通过npm test运行,它都会执行当前目录下所有的*.test.js 或 *.spec.js 文件、完成测试。
基本使用
用例的表示
表示测试用例是一个测试框架提供的最基本的 API , Jest 内部使用了 Jasmine 2 来进行测试,故其用例语法与 Jasmine 相同。test()函数来描述一个测试用例,举个简单的例子:
// hello.js
module.exports = () => 'Hello world'
// hello.test.js
let hello = require('hello.js')
test('should get "Hello world"', () => {
expect(hello()).toBe('Hello world') // 测试成功
// expect(hello()).toBe('Hello') // 测试失败
})
其中toBe('Hello world')便是一句断言( Jest 管它叫 “matcher” ,想了解更多 matcher 请参考
文档
)
。写完了用例,运行在项目目录下执行npm test,即可看到测试结果:
若测试失败,会标识出失败的断言位置,结果如下:
用例的预处理或后处理
有时我们想在测试开始之前进行下环境的检查、或者在测试结束之后作一些清理操作,这就需要对用例进行预处理或后处理。对测试文件中所有的用例进行统一的预处理,可以使用 beforeAll() 函数;而如果想在每个用例开始前进行都预处理,则可使用 beforeEach() 函数。至于后处理,也有对应的 afterAll() 和 afterEach() 函数。
如果只是想对某几个用例进行同样的预处理或后处理,可以将先将这几个用例归为一组。使用 describe() 函数即可表示一组用例,再将上面提到的四个处理函数置于 describe() 的处理回调内,就实现了对一组用例的预处理或后处理:
describe('test testObject', () => {
beforeAll(() => {
// 预处理操作
})
test('is foo', () => {
expect(testObject.foo).toBeTruthy()
})
test('is not bar', () => {
expect(testObject.bar).toBeFalsy()
})
afterAll(() => {
// 后处理操作
})
})
测试异步代码
异步代码的测试,关键点在于告知测试框架测试何时完成,让其在恰当的时机进行断言。针对几种常见的异步代码形式, Jest 也提供了相应的异步测试语法。首先对于异步回调,向其传入并执行 done 函数, Jest 会等 done 回调执行结束后,结束测试:
// asyncHello.js
module.exports = (name, cb) => setTimeout(() => cb(`Hello ${name}`), 1000)
// asyncHello.test.js
let asyncHello = require('asyncHello.js')
test('should get "Hello world"', (done) => {
asyncHello('world', (result) => {
expect(result).toBe('Hello world')
done()
})
})
此外,对于 Promise 控制的异步代码,可以直接在 then 回调中进行断言,只要保证在用例中返回该 Promise 对象即可:
// promiseHello.js
module.exports = (name) => {
return new Promise((resolve) => {
setTimeout(() => resolve(`Hello ${name}`), 1000)
})
}
// promiseHello.test.js
let promiseHello = require('promiseHello.js')
it('should get "Hello world"', () => {
expect.assertions(1); // 确保至少有一个断言被调用,否则测试失败
return promiseHello('world').then((data) => {
expect(data).toBe('Hello world')
})
})
Jest 也支持 async/await 语法的测试,无需多余的操作,只要在 await 后进行断言即可,和同步测试的写法一致。
测试覆盖率
Jest 内置了测试覆盖率工具
istanbul
,要开启,可以直接在命令中添加 --coverage 参数,或者在 package.json 文件进行更详细的
配置
。
运行 istanbul 除了会再终端展示测试覆盖率情况,还会在项目下生产一个 coverage 目录,内附一个测试覆盖率的报告,让我们可以清晰看到分支的代码的测试情况。比如下面这个例子:
// branches.js
module.exports = (name) => {
if (name === 'Levon') {
return `Hello Levon`
} else {
return `Hello ${name}`
}
}
// branches.test.js
let branches = require('../branches.js')
describe('Multiple branches test', ()=> {
test('should get Hello Levon', ()=> {
expect(branches('Levon')).toBe('Hello Levon')
});
// test('should get Hello World', ()=> {
// expect(branches('World')).toBe('Hello World')
// });
})
运行 jest --coverage 可看到生产的报告里展示了代码的覆盖率和未测试的行数:
如果我们把branches.test.js中的注释去掉,跑遍测试对象中的所有分支,测试覆盖率就是100%了:
在前端项目中使用
搭配React和其它框架