做者介绍:林列欢,美团点评点餐团队成员。
前端测试工具也和前端的框架同样纷繁复杂,其中常见的测试工具,大体可分为测试框架、断言库、测试覆盖率工具等几类。在正式开始本文以前,咱们先来大体了解下它们:html
测试框架的做用是提供一些方便的语法来描述测试用例,以及对用例进行分组。前端
测试框架可分为两种: TDD (测试驱动开发)和 BDD (行为驱动开发),我理解二者间的区别主要是一些语法上的不一样,其中 BDD 提供了提供了可读性更好的用例语法,至于详细的区别可参见 The Difference Between TDD and BDD 一文。react
常见的测试框架有 Jasmine, Mocha 以及本文要介绍的 Jest 。git
断言库主要提供语义化方法,用于对参与测试的值作各类各样的判断。这些语义化方法会返回测试的结果,要么成功、要么失败。常见的断言库有 Should.js, Chai.js 等。github
用于统计测试用例对代码的测试状况,生成相应的报表,好比 istanbul 。npm
Jest 是 Facebook 出品的一个测试框架,相对其余测试框架,其一大特色就是就是内置了经常使用的测试工具,好比自带断言、测试覆盖率工具,实现了开箱即用。json
而做为一个面向前端的测试框架, Jest 能够利用其特有的快照测试功能,经过比对 UI 代码生成的快照文件,实现对 React 等常见框架的自动测试。segmentfault
此外, Jest 的测试用例是并行执行的,并且只执行发生改变的文件所对应的测试,提高了测试速度。目前在 Github 上其 star 数已经破万;而除了 Facebook 外,业内其余公司也开始从其它测试框架转向 Jest ,好比 Airbnb 的尝试 ,相信将来 Jest 的发展趋势仍会比较迅猛。前端工程化
Jest 能够经过 npm 或 yarn 进行安装。以 npm 为例,既可用npm install -g jest
进行全局安装;也能够只局部安装、并在 package.json 中指定 test 脚本:promise
{
"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%了:
针对前端框架的测试, Jest 的一大特点就是提供了快照测试功能。首次运行快照测试,会让 UI 框架生产一个可读的快照,再次测试时便会经过比对快照文件和新 UI 框架产生的快照判断测试是否经过。对于 React ,咱们能够经过下面的方法生产一个快照:
import React from 'react';
import Link from '../Link.react';
import renderer from 'react-test-renderer';
it('renders correctly', () => {
const tree = renderer.create(
<Link page="http://www.facebook.com">Facebook</Link>
).toJSON();
expect(tree).toMatchSnapshot();
});
复制代码
运行测试,咱们能够看到生成一个快照文件以下:
exports[`renders correctly 1`] = `
<a
className="normal"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Facebook
</a>
`;
复制代码
这个可读的快照文件以可读的形式展现了 React 渲染出的 DOM 结构。相比于肉眼观察效果的 UI 测试,快照测试直接由Jest进行比对、速度更快;并且因为直接展现了 DOM 结构,也能让咱们在检查快照的时候,快速、准确地发现问题。
除了 React ,Jest 文档中也提供了针对其余框架进行测试的指南。
若是你的项目中已经使用了别的测试框架,好比 Mocha,有一个第三方工具jest-codemods能够自动把用例迁移成 Jest 的用例,下降了迁移成本。
近几年前端工程化的发展风起云涌,可是前端自动化测试这块内容你们却彷佛不过重视。虽然项目迭代过程当中会有专门的测试人员进行测试,但等他们来进行测试时,代码已经开发完成的状态。与之相比,若是咱们在开发过程当中就进行了测试(直接采用 TDD 开发模式、或者针对既有的模块写用例),会有以下的好处:
固然,凡事都有两面性,好处虽然明显,却并非全部的项目都值得引入测试框架,毕竟维护测试用例也是须要成本的。对于一些需求频繁变动、复用性较低的内容,好比活动页面,让开发专门抽出人力来写测试用例确实得不偿失。
而那些适合引入测试场景大概有这么几个:
以上就是我对前端测试的一点浅见,欢迎斧正。