为什么要写单元测试
- 减少bug
- 提高代码质量,保证你的代码是可测试的
- 放心重构
当你每个方法都写了单元测试的时候,你每一个改动都会影响相应的单元测试,这样你不用费尽心思的考虑哪里会有影响,特别是复杂项目或非核心功能(不易被测试到),从而导致bug的产生。
当你的代码不可测试的时候,就得考虑你的代码是否需要重构的。好的代码应该是职责分明且单一,颗粒度小且易于测试。
当你重构时,特别是大范围的重构,你就有勇气和信心了。
那么单元测试需要有那些要素呢
- 测试框架
- 测试报表
- 测试覆盖率
- 断言
- mock
安装
npm install mocha
package.json
"scripts": {
"test": "mocha --recursive --require babel-core/register tests/Js/test"
}
mocha默认会找到项目的根目录下的 test目录,但是很多人项目目录中单元测试目录并不是test,而是在/tests/Js/test中,在scripts中,后面加上单元测试路径,就可以修改默认地址
recursive参数代表查找 目录的所有子目录下的单元测试,否则只会查找当前目录下的单元测试
node中并不支持某些es6语法,需要通过babel编译,所以需要添加 --require babel-core/register
同时需要在项目根目录添加.babelrc文件
{
"presets": [ "es2015" ]
}
现在在 tests/Js/test目录下创建一个文件 test.js
var assert = require('assert');
describe('Array', function() {
describe('#indexOf()', function() {
it('should return -1 when the value is not present', function() {
assert.equal([1,2,3].indexOf(4), -1);
});
});
});
命令行中执行 npm run test 结果如下
以上我们知道了如何引入前端测试框架,es6语法问题,执行路径问题
有时候命令行的结果看着不明了,想要输出测试报告呢
这时候可以使用 mochawesome
安装
npm install --save-dev mochawesome
package.json
"scripts": {
"test": "mocha --recursive --reporter mochawesome --require babel-core/register tests/Js/test"
}
在scripts命令中,添加 --reporter mochawesome
执行命令
npm run test
结果如图,将会生成 html文件和json文件
"代码覆盖率"(code coverage)。它有四个测量维度。
- 行覆盖率(line coverage):是否每一行都执行了
- 函数覆盖率(function coverage):是否每个函数都调用了
- 分支覆盖率(branch coverage):是否每个if代码块都执行了
- 语句覆盖率(statement coverage):是否每个语句都执行了
那么如何知道每个js的覆盖率呢
这时候用到了 istanbul 和 babel-istanbul
安装
npm install istanbul
npm install babel-istanbul
package.json
"scripts": {
"test:cover": "babel-node ./node_modules/.bin/babel-istanbul cover _mocha -- tests/Js/* -R spec --recursive
}
istanbul他也是不支持一些es6语法的,所以也需要babel转译
使用cover参数结合mocha,--代表后面参数传递给mocha
执行命令
npm run test:cover
结果如图
Mocha本身不带断言库,所以必须先引入断言库。
我们这里使用 chai
npm install chai
chaijs有三种断言风格,详细查看官网
jquery作为大部分都在使用的库,那么如何对这类的代码进行单元测试呢
比如以下代码
hide-element.js
export const hideElement = ($element) => {
$element.on('click', '.hide', function() {
$(this).hide();
});
};
首先node环境和浏览器环境是不一样的,所以我们跑这类的单元测试就需要模拟出浏览器环境
我们需要安装 jsdom
npm install jsdom
test.js 如下
const assert = require('chai').assert;
const { hideElement } = require('xxxx/hide-element.js');
describe('test:hide-element.js', function(done) {
before(function() {
let { JSDOM } = require('jsdom');
let dom = new JSDOM(`<!DOCTYPE html><html><body><div class="hide"></div></body></html>`,{
url: 'http://127.0.0.1/',
referrer: 'http://127.0.0.1/',
contentType: 'text/html',
userAgent: 'Mellblomenator/9000',
includeNodeLocations: true,
});
global.window = dom.window;
global.$ = require('jquery');
hideElement($('body'));
});
it('click event', function() {
$('body').find('.hide').trigger('click');
assert.equal($('.hide').css('display'), 'none');
});
});
- mocha 由 describe,it基本元素组成
- mocha 有四个钩子函数 before,after,beforeEach,afterEach
- 因为node中引入依赖时会缓存模块,初始化jsdom环境时,最好在before中,防止污染jsdom环境,导致不同的单元测试之间互相影响
需要测试的代码中依赖了其他的模块时,为了测试需要测试的代码,而不去关心依赖的模块,这时候我们需要 sinon 去mock掉相关依赖
demo.js
import api from 'api';
export const demo = (arg) => {
if (arg == 1) {
return api.get({
params: params
});
}
return 'ok';
};
test.js
import { demo } from 'xx/demo.js';
const assert = require('chai').assert;
const sinon = require('sinon');
import api from 'api';
describe('demo', function() {
it('demo(1)', function() {
//mock api的get方法,并且指定返回值为 'N'
let apiGet = sinon.stub(api, 'get').returns('N');
let expectedParams = {params: 'yes'};
let res = demo(1);
//automate clean-up,防止影响其他单元测试
apiGet.restore();
//断言调用api.get 是传入的参数为 {params: 'yes'}
sinon.assert.calledWith(apiGet, expectedParams);
assert.equal(res, 'N');
});
});
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。