写这篇文章的初衷是,有个朋友来找我写单元测试...自己懵逼了,开发了这么久,还真没有好好写过测试用例,然后就和我朋友掰扯,我的论点是,前端开发没必要写单元测试,一般公司都有端对端测试就是点点点...开发人员一旦书写测试用例,无形增加人员的开销,或者是延缓迭代速率...他一句话,上头需要...两人哑口无言乘着空闲的时候就查阅了各种资料
思考一:是否有必要单元测试?(团队是否拥有测试团队的两种情况)
思考二:实在要编写单元测试如何编写优雅的测试用例模块?大家有空不妨告诉我你们公司会用到单元测试么?有必要么?
下面先自我归总下
Mocha
单元测试的基础api使用和注意点
什么是Mocha
字如其名,mocha(摩卡咖啡)其官网文档也是如此一般
JavaScript 具有多种工具和框架,这些工具和框架可以在 Node.js 以及浏览器中进行测试驱动开发,例如:Jest, Jasmine, Mocha, QUnit, Karma, Cypress 等。
而mocha
就是测试驱动开发中众多工具之一
第一个测试套件
安装 所需依赖
npm i mocha -g 全局安装
npm i mocha --save-dev 项目依赖中安装
终端中执行mocha
将在当前目录中需要test/
文件夹并执行对应的测试用用例文件
下面我们建立个test目录并新建index.test.js
文件
// index.test.js
function add(v,v2){
return v + v2;
}
describe('# add()', function() {
it('should return 3 when the value 1 、 2', function() {
expect(add()).to.be.equal(3)
})
})
以上一个测试add()
方法运算逻辑是否正常的功能测试用例就算是编写好了
但是真实项目中,我们一般不会如此简单的业务逻辑;一般都会包含测试异步代码、测试同步代码,以及一些骚操作提高效率
异步代码测试
异步代码在现代开发中很常见,mocha针对异步代码也有对应的解决方案
1.使用回调方法
2.使用 Promise (用于支持 Promise 的环境)
3.使用async
/await
(用于支持异步 function 的环境)
异步接口请求的时候可以使用supertest提供http请求方案
1.使用回调方法
//异步接口请求测试
it('异步请求应该返回一个对象', function(done){
supertest
.get('https://api.github.com')
.end(function(err, res){
expect(res).to.be.an('object');
done();
});
});
如上使用 supertest
方法能很好的进行异步回调处理
var expect = require('chai').expect;
function asyncFn (string, callback){
var withCallback \= typeof callback \=== 'function';
try {
let hash = string;
withCallback && callback(null, hash);
} catch (e) {
if (withCallback) {
callback(e);
} else {
throw e;
}
}
}
describe('# asyncFn()', function() {
it('异步函数回调处理', function(done) {
asyncFn('test',function(err,str){
// 使用 error 来调用 done() 回调函数,
// 来终止带有错误的测试
if(err) return done(err);
expect(str).to.be.equal('test');
// 调用 done() 回调函数来终止测试 done();
})
})
})
如上使用回调函数处理异步代码
2.使用 Promise
function promiseAsyncFn (str){
return new Promise((reolve,reject)=>{
asyncFn(string, (err, str) => {
return err ? reject(err) : resolve(str);
})
})
}
describe('# asyncFn()', function() {
it('异步函数回调处理 resolve', function(done) {
return promiseAsyncFn('test').then(function()=>{
expect(str).to.be.equal('test');
done()
})
})
it('异步函数回调处理 reject', function(done) {
return promiseAsyncFn('test').catch(
function(err)=>{
expect(function(){throw err}).to.throw(TypeError, 'The "data" argument must be one of type string, TypedArray, or DataView. Received type number');
done(err)
})
})
})
3.使用 async /await
describe('# asyncFn()', function() {
it('异步函数回调处理 resolve', async function(done) {
let data = await promiseAsyncFn('test')
expect(data).to.be.equal('test');
done()
})
it('异步函数回调处理 reject',async function(done) {
await promiseAsyncFn('test').catch(
function(err)=>{
expect(function(){throw err}).to.throw(TypeError, 'The "data" argument must be one of type string, TypedArray, or DataView. Received type number');
done(err)
})
})
})
看完上面三种异步代码的处理方法熟悉js的小伙伴要开始扔砖了,和JavaScript好像一样,我写完之后发现确实一样,但是我们不能大意其中done()的特性!
done()
使用的主意事项
- it() 用例中必须调用done()结束测试,否则测试将一直运行直到超时。
- it() 中done()不能被调用多次,有且仅有一次
- done() 参数可以是错误,并可显示指定错误
断言库chai
细心的同学应该发现了每个it用例汇总总会出现个expect
,这是配合mocha处理错误判断异常的断言库chai
模块中的一个方法,其他如Expect.js,Should.js;mocha中也拥有内置的assert
模块但是我们优先使用开发中更加常用的以及功能强大的chai
npm i --save-dev chai
expect风格
常用的expect断言风格如下
// 相等或不相等
expect(4 + 5).to.be.equal(9);
expect(4 + 5).to.be.not.equal(10);
expect(foo).to.be.deep.equal({ bar: 'baz' });
// 布尔值为true
expect('everthing').to.be.ok;
expect(false).to.not.be.ok;
// typeof
expect('test').to.be.a('string');
expect({ foo: 'bar' }).to.be.an('object');
expect(foo).to.be.an.instanceof(Foo);
// include
expect([1,2,3]).to.include(2);
expect('foobar').to.contain('foo');
expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo');
// empty
expect([]).to.be.empty;
expect('').to.be.empty;
expect({}).to.be.empty;
// match
expect('foobar').to.match(/^foo/);
观察规律我们不难发现:expect
断言的写法都是一样的。头部是expect
方法,尾部是断言方法,比如equal
、a
/an
、ok
、match
等。两者之间使用to
或to.be
连接。
Assert风格
var assert = require('chai').assert;
var numbers = [1, 2, 3, 4, 5];
assert.isArray(numbers, 'is array of numbers');
assert.include(numbers, 2, 'array contains 2');
assert.lengthOf(numbers, 5, 'array contains 5 numbers');
Should 风格
var should = require('chai').should();
var numbers = [1, 2, 3, 4, 5];
numbers.should.be.an('array').that.includes(2);
numbers.should.have.lengthOf(5);
mocha中的hooks
Mocha 提供了创建测试 hooks 的功能,hooks 基本上是配置为在测试之前或之后运行的逻辑。它们对于设置测试的前提条件或者在测试后清理资源很有用。使用默认的 BDD 接口,Mocha 提供了四个 hooks:
-
before()
- 在块中的第一个测试用例之前运行一次 -
beforeEach()
- 在每个测试用例前运行 -
afterEach()
- 在每个测试用例之后运行 -
after()
- 在块中的最后一个测试用例之后运行一次
执行顺序如下
before() -> beforeEach() -> test() -> afterEach() -> after()
创建一个简单的hooks
describe('hooks', function() {
before(function() {
// 在本区块的所有测试用例之前执行
});
after(function() {
// 在本区块的所有测试用例之后执行
});
beforeEach(function() {
// 在本区块的每个测试用例之前执行
});
afterEach(function() {
// 在本区块的每个测试用例之后执行
});
// test cases
});
骚操作
mocha 同时具备一些自身可配参数,比如修改用例超时时间、递归执行test文件夹下的所有路径、使用skip()、only()
控制it、describe是否执行...
1. 包含/排除测试skip()、only()
// 此测试套件中的测试将运行
describe.only('#flattenArray()', function() {
describe('#二级-1()', function() {
it.only('should merge two arrays', function() {})
it('should merge two arrays123123', function() {})
})
// it('should flatten array', function() {})
describe.skip('#二级-2()', function() {
it('should merge two arrays', function() {})
})
})
// 此测试套件中的测试无法运行
describe('#mergeArray()', function() {
it('should merge two arrays', function() {})
})
结果如下:
only会影响测试套件、测试用例
如上在第一个describe使用了only之后相邻的测试用例则被规避,不执行了
2. 重复测试
mocha 提供了 this.retries () 函数,该函数可以让您指定允许重试的次数。对于每次重试,Mocha 都会运行重新 beforeEach () 和 afterEach () hooks,但不会重新运行 before () 和 after () 钩子。
// this.retries() 函数的用法
describe('test medium site', function() {
// 这个套件中所有失败的测试都会重试2次
this.retries(2)
it('should load medium homepage', function() {
// 这个测试用例测试失败后,会被重复5次
this.retries(5)
cy.visit('https://medium.com')
})
})
3. 慢速定义this.slow(value)
设定一个值界定当前测试用例超过这个临界点是慢的
describe('slow test', function() {
// 在一秒之后,测试将被认为是缓慢的
this.slow(1000)
// 在指定的一秒钟之后完成
it('should be complete in a second', function(done) {
setTimeout(done, 1500)
})
// 立即完成
it('should be complete instantly', function() {})
})
4. 超时定义this.timeout(value)
可设置一个数值告诉mocha最大等待时间,超出时间则超时,mocha默认超时时间是2S
describe('some time-consuming operation', function() {
// 给这个测试用例设置 5 秒的超时时间
this.timeout(5000)
before('some long step', function(done) {
// 设置 hooks 的超时时间
this.timeout(2500)
setTimeout(done, 2250)
})
it('should take less than 200ms', function(done) {
// 为当前测试用例设置超时时间
this.timeout(200)
setTimeout(done, 150)
})
})
MOCHA CLI
mocha --watch
// 标志指示 Mocha 监听测试文件的变动并重新运行测试。这对于在开发过程中编写测试非常有用。
mocha --async-only
// 标志强制所有测试必须传递回调函数或返回一个 promise,从而异步执行。对于未指定回调函数或者未返回 promise 的测试将被标记为失败
mocha --bail
// 标志指定只要有一个测试用例没有通过,就停止后面所有的测试用例。这对于持续继承很有用
mocha -t 3000
// 等价于`this.timeout()`方法,这个作用于全局
mocha --no-timeouts
// 等价于`--timeout 0``this.timeout(0)`,表示~~~~禁用超时限制
mocha --slow 100
// 默认的阈值是 75ms,标记速率的阈值
mocha --require should
// 标志允许您导入在测试文件中使用的模块 / 库(例如断言库),而不是在代码中手动调用 require ()。
mocha -R list
// 可以指定测试报告的格式。默认的是`spec`格式。Mocha 还允许您使用此标志指定第三方报告格式。
mocha 还能使用将测试报告跑在浏览器中,结合一些插件能提高一定效率;
说到这里,我认为一个成熟的开发团队,应该是有必要也是必须有意识的书写测试用例,为后人以及新需求搭建稳固的基础,啦啦啦
欢迎来到自己的blog小站留言
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。