本文主要介绍前端单元测试的一些技术方案。
单元测试的技术方案很多,不同工具之间有互相协同,也存在功能重合,给我们搭配测试方案带来不小的困难,而且随着 ES6
, TypeScript
的出现,单元测试又增加了很多其他步骤,完整配置起来往往需要很大的时间成本。我希望通过对这些工具的各自作用的掌握,了解完整的前端测试技术方案。前端单元测试的领域也很多,这里主要讲对于前端组件如何进行单元测试,最后会主要介绍下对于 React
组件的一些测试方法总结。
微信公众号 |程序员小濠(主要分享软件测试的学习资源,帮助想转行、进阶、小白成为高级测试工程师…)
通用测试
单元测试最核心的部分就是做断言,比如传统语言中的 assert
函数,如果当前程序的某种状态符合 assert
的期望此程序才能正常执行,否则直接退出应用。所以我们可以直接用 Node
中自带的 assert
模块做断言。
用最简单的例子做个验证
function multiple(a, b) {
let result = 0;
for (let i = 0; i < b; ++i)
result += a;
return result;
}
复制代码
const assert = require('assert');
assert.equal(multiple(1, 2), 3));
复制代码
这种例子能够满足基础场景的使用,也可以作为一种单元测试的方法。
nodejs
自带的 assert
模块提供了下面一些断言方法,只能满足一些简单场景的需要。
assert.deepEqual(actual, expected[, message])
assert.deepStrictEqual(actual, expected[, message])
assert.doesNotMatch(string, regexp[, message])
assert.doesNotReject(asyncFn[, error][, message])
assert.doesNotThrow(fn[, error][, message])
assert.equal(actual, expected[, message])
assert.fail([message])
assert.ifError(value)
assert.match(string, regexp[, message])
assert.notDeepEqual(actual, expected[, message])
assert.notDeepStrictEqual(actual, expected[, message])
assert.notEqual(actual, expected[, message])
assert.notStrictEqual(actual, expected[, message])
assert.ok(value[, message])
assert.rejects(asyncFn[, error][, message])
assert.strictEqual(actual, expected[, message])
assert.throws(fn[, error][, message])
复制代码
自带的 assert
不是专门给单元测试使用, 提供的错误信息文档性不好,上面的 demo
最终执行下来会产生下面的报告:
$ node index.js
assert.js:84
throw new AssertionError(obj);
^
AssertionError [ERR_ASSERTION]: 2 == 3
at Object.<anonymous> (/home/quanwei/git/index.js:4:8)
at Module._compile (internal/modules/cjs/loader.js:778:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:789:10)
at Module.load (internal/modules/cjs/loader.js:653:32)
at tryModuleLoad (internal/modules/cjs/loader.js:593:12)
at Function.Module._load (internal/modules/cjs/loader.js:585:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:831:12)
at startup (internal/bootstrap/node.js:283:19)
at bootstrapNodeJSCore (internal/bootstrap/node.js:623:3)
复制代码
由于自带的模块依赖 Node
自身的版本,没办法自由升级,所以使用内置的包灵活性有时候不太够,另外我们很多断言函数也需要在浏览器端执行,所以我们需要同时支持浏览器和 Node
端的断言库。同时观察上面的输出可以发现,这个报告更像是程序的错误报告,而不是一个单元测试报告。而我们在做单元测时往往需要断言库能够提供良好的测试报告,这样才能一目了然地看到有哪些断言通过没通过,所以使用专业的单元测试断言库还是很有必要。
chai
chai
是目前很流行的断言库,相比于同类产品比较突出。chai
提供了 TDD (Test-driven development)和 BDD (Behavior-driven development) 两种风格的断言函数,这里不会过多介绍两种风格的优缺,本文主要以 BDD
风格做演示。
TDD 风格的 chai
var assert = require('chai').assert
, foo = 'bar'
, beverages = { tea: [ 'chai', 'matcha', 'oolong' ] };
assert.typeOf(foo, 'string'); // without optional message
assert.typeOf(foo, 'number', 'foo is a number'); // with optional message
assert.equal(foo, 'bar', 'foo equal `bar`');
assert.lengthOf(foo, 3, 'foo`s value has a length of 3');
assert.lengthOf(beverages.tea, 3, 'beverages has 3 types of tea');
复制代码
chai
比 Node
自带的 assert
增加了一个断言说明参数,可以通过这个参数提高测试报告的可读性
$ node chai-assert.js
/home/quanwei/git/learn-tdd-bdd/node_modules/chai/lib/chai/assertion.js:141
throw new AssertionError(msg, {
^
AssertionError: foo is a number: expected 'bar' to be a number
at Object.<anonymous> (/home/quanwei/git/learn-tdd-bdd/chai-assert.js:6:8)
at Module._compile (internal/modules/cjs/loader.js:778:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:789:10)
at Module.load (internal/modules/cjs/loader.js:653:32)
at tryModuleLoad (internal/modules/cjs/loader.js:593:12)
at Function.Module._load (internal/modules/cjs/loader.js:585:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:831:12)
at startup (internal/bootstrap/node.js:283:19)
at bootstrapNodeJSCore (internal/bootstrap/node.js:623:3)
复制代码
BDD 风格的 chai
chai
的 BDD
风格使用 expect
函数作为语义的起始,也是目前几乎所有 BDD
工具库都遵循的风格。
chai
的 expect
断言风格如下
expect(foo).to.be.a('string');
expect(foo).to.equal('bar');
expect(foo).to.have.lengthOf(3);
复制代码
BDD
的思想就是写单元测试就像写产品需求,而不关心内部逻辑,每一个用例阅读起来就像一篇文档。例如下面的用例:
- foo 是一个字符串 ->
expect(foo).to.be.a('string')
- foo 字符串里包含 'bar' ->
expect(foo).to.include('bar')
- foo 字符串里不包含 'biz' ->
expect(foo).to.not.include('biz')
可以看到这种风格的测试用例可读性更强。
其他的断言库还有 expect.js
should.js
better-assert , unexpected.js 这些断言库都只提供纯粹的断言函数,可以根据喜好选择不同的库使用。
有了断言库之后我们还需要使用测试框架将我们的断言更好地组织起来。
mocha 和 Jasmine
mocha
是一个经典的测试框架(Test Framework),测试框架提供了一个单元测试的骨架,可以将不同子功能分成多个文件,也可以对一个子模块的不同子功能再进行不同的功能测试,从而生成一份结构型的测试报告。例如 mocha
就提供了describe
和 it
描述用例结构,提供了 before
, after
, beforeEach
, afterEach
生命周期函数,提供了 describe.only
,describe.skip
, it.only
, it.skip
用以执行指定部分测试集。
const { expect } = require('chai');
const { multiple } = require('./index');
describe('Multiple', () => {
it ('should be a function', () => {
expect(multiple).to.be.a('function');
})
it ('expect 2 * 3 = 6', () => {
expect(multiple(2, 3)).to.be.equal(6);
})
})
复制代码
测试框架不依赖底层的断言库,哪怕使用原生的 assert
模块也可以进行。给每一个文件都要手动引入 chai
比较麻烦 ,这时候可以给 mocha
配置全局脚本,在项目根目录 .mocharc.js
文件中加载