写这篇文章的初衷是,有个朋友来找我写单元测试...自己懵逼了,开发了这么久,还真没有好好写过测试用例,然后就和我朋友掰扯,我的论点是,前端开发没必要写单元测试,一般公司都有端对端测试就是点点点...开发人员一旦书写测试用例,无形增加人员的开销,或者是延缓迭代速率...他一句话,上头需要...两人哑口无言

乘着空闲的时候就查阅了各种资料
思考一:是否有必要单元测试?(团队是否拥有测试团队的两种情况)
思考二:实在要编写单元测试如何编写优雅的测试用例模块?

大家有空不妨告诉我你们公司会用到单元测试么?有必要么?

下面先自我归总下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()使用的主意事项
  1. it() 用例中必须调用done()结束测试,否则测试将一直运行直到超时。
  2. it() 中done()不能被调用多次,有且仅有一次
  3. 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方法,尾部是断言方法,比如equala/anokmatch等。两者之间使用toto.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:

  1. before()- 在块中的第一个测试用例之前运行一次
  2. beforeEach()- 在每个测试用例前运行
  3. afterEach()- 在每个测试用例之后运行
  4. 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() {})
})

结果如下:
image.png
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小站留言


好久不见
685 声望7 粉丝

努力吃胖,努力健身,快乐打球,快乐code