學習測試框架Mocha

龍恩0707發表於2018-02-05

學習測試框架Mocha

注意:是參考阮老師的文章來學的。雖然阮老師有講解,但是覺得自己敲一遍,然後記錄一遍效果會更好點。俗話說,好記性不如爛筆頭。

   Mocha 是javascript測試框架之一,可以在瀏覽器和Node環境下使用,除了Mocha測試框架之外,類似的測試框架還有Jasmine, Karma, Tape等。
可以使用npm全域性安裝:如下命令:

npm install -g mocha

也可以作為專案的依賴進行安裝,如下命令:

npm install --save-dev mocha

如下所有的測試程式碼在github上,請檢視github上的程式碼

Mocha的作用是執行測試指令碼,我們先來編寫一個js程式碼吧,下面是一個簡單的加法模組 add.js程式碼:

function add(x, y) {
  return x + y;
}
module.exports = add;

要測試上面的程式碼是否對的,因此就要編寫測試指令碼,測試指令碼與所要測試的原始碼指令碼同名,但是字尾名為 .test.js或 .spec.js, 如:xx.test.js 或 xx.spec.js,比如上面的add.js的測試指令碼可以叫 add.test.js 或 add.spec.js,因此我們可以在add.js的同目錄下新建 add.test.js,(可以檢視demo1檔案程式碼)
編寫程式碼如下:

var add = require('./add.js');
var expect = require('chai').expect;

describe('加法函式的測試', function() {
  it('1加1應該等於2', function() {
    expect(add(1, 1)).to.be.equal(2);
  });
});

如上程式碼就是一個測試指令碼程式碼,測試指令碼可以包含一個或多個describe塊,describe塊稱為 "測試套件",表示一組相關的測試,它是一個函式,有兩個引數,第一個引數是測試套件的名稱,第二個引數是一個實際執行的函式。

每個describe塊也可以包含一個或多個it塊,it塊稱為 "測試用例",表示一個單獨的測試,是測試的最小單位,它也是一個函式,第一個引數也是測試用例的名稱,第二個引數是一個實際執行的函式。

二. 理解斷言庫
斷言庫可以理解為比較函式,也就是斷言函式是否和預期一致,如果一致則表示測試通過,如果不一致表示測試失敗。mocha本身是不包括斷言庫的,所以必須引入第三方斷言庫的,目前比較受歡迎的斷言庫有 should.js, expect.js, chai.
should.js BDD風格
expect.js expect風格的斷言
chai expect(), assert() 和 should的斷言
Mocha預設使用的是BDD的風格。expect和should都是BDD的風格,二者使用相同的鏈式語言來組織斷言的,但不同在於他們初始化斷言的方式,expect使用
建構函式來建立斷言物件例項,而should通過為 Object.prototype新增方法來實現斷言(should不支援IE),expect直接指向 chai.expect,
should則是 chai.should();

上面的程式碼中 expect 是斷言的意思,該作用是判斷原始碼的實際執行結果與預期結果是否一致,如果不一致就丟擲一個錯誤,因此在執行上面程式碼之前,
我們需要在專案中安裝 chai, 如下命令:

npm install --save-dev chai

所有的測試用例(it塊)都應該含有一句或多句斷言,是編寫測試用例的關鍵,Mocha本身不包含斷言,斷言是由斷言庫來實現的,因此需要先引入斷言庫。
如下程式碼:

var expect = require('chai').expect;

上面程式碼是引用 chai 斷言庫,使用的是 expect斷言風格。

expect 官網API(http://chaijs.com/api/bdd/).

如下是一些常用的比較;

// equal 相等或不相等
expect(4 + 5).to.be.equal(9);
expect(4 + 5).to.be.not.equal(10);
expect('hello').to.equal('hello');  
expect(42).to.equal(42);  
expect(1).to.not.equal(true);  
expect({ foo: 'bar' }).to.not.equal({ foo: 'bar' });  
expect({ foo: 'bar' }).to.deep.equal({ foo: 'bar' });

// above 斷言目標的值大於某個value,如果前面有length的鏈式標記,則可以用來判斷陣列長度或者字串長度
expect(10).to.be.above(5);
expect('foo').to.have.length.above(2);  
expect([ 1, 2, 3 ]).to.have.length.above(2); 
類似的還有least(value)表示大於等於;below(value)表示小於;most(value)表示小於等於

// 判斷目標是否為布林值true(隱式轉換)
expect('everthing').to.be.ok;
expect(1).to.be.ok;  
expect(false).to.not.be.ok;
expect(undefined).to.not.be.ok;  
expect(null).to.not.be.ok; 

// true/false 斷言目標是否為true或false
expect(true).to.be.true;  
expect(1).to.not.be.true;
expect(false).to.be.false;  
expect(0).to.not.be.false;

// null/undefined 斷言目標是否為null/undefined
expect(null).to.be.null;  
expect(undefined).not.to.be.null;
expect(undefined).to.be.undefined;  
expect(null).to.not.be.undefined;


// NaN  斷言目標值不是數值
expect('foo').to.be.NaN;
expect(4).not.to.be.NaN;

// 判斷型別大法(可以實現上面的一些例子):a/an
expect('test').to.be.a('string');
expect({ foo: 'bar' }).to.be.an('object');
expect(foo).to.be.an.instanceof(Foo);
expect(null).to.be.a('null');  
expect(undefined).to.be.an('undefined');
expect(new Error).to.be.an('error');
expect(new Promise).to.be.a('promise');

// 包含關係:用來斷言字串包含和陣列包含。如果用在鏈式呼叫中,可以用來測試物件是否包含某key 可以混著用。
expect([1,2,3]).to.include(2);
expect('foobar').to.contain('foo');
expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo');

// 判斷空值
expect([]).to.be.empty;
expect('').to.be.empty;
expect({}).to.be.empty;

// match
expect('foobar').to.match(/^foo/);
    
// exist 斷言目標既不是null也不是undefined
var foo = 'hi' , bar = null, baz;
expect(foo).to.exist;  
expect(bar).to.not.exist;  
expect(baz).to.not.exist;

// within斷言目標值在某個區間範圍內,可以與length連用
expect(7).to.be.within(5,10);  
expect('foo').to.have.length.within(2,4);  
expect([ 1, 2, 3 ]).to.have.length.within(2,4);

// instanceOf 斷言目標是某個構造器產生的事例
var Tea = function (name) { this.name = name; } , Chai = new Tea('chai');
expect(Chai).to.be.an.instanceof(Tea);  
expect([ 1, 2, 3 ]).to.be.instanceof(Array); 

// property(name, [value])  斷言目標有以name為key的屬性,並且可以指定value斷言屬性值是嚴格相等的,此[value]引數為可選,如果使用deep鏈式呼叫,可以在name中指定物件或陣列的引用表示方法
// simple referencing
var obj = { foo: 'bar' };  
expect(obj).to.have.property('foo');  
expect(obj).to.have.property('foo', 'bar');// 類似於expect(obj).to.contains.keys('foo')

// deep referencing
var deepObj = {  
  green: { tea: 'matcha' },
  teas: [ 'chai', 'matcha', { tea: 'konacha' } ]
};
expect(deepObj).to.have.deep.property('green.tea', 'matcha');  
expect(deepObj).to.have.deep.property('teas[1]', 'matcha');  
expect(deepObj).to.have.deep.property('teas[2].tea', 'konacha'); 

// ownproperty 斷言目標擁有自己的屬性,非原型鏈繼承
expect('test').to.have.ownProperty('length'); 

// throw 斷言目標丟擲特定的異常
var err = new ReferenceError('This is a bad function.');  
var fn = function () { throw err; }  
expect(fn).to.throw(ReferenceError);  
expect(fn).to.throw(Error);  
expect(fn).to.throw(/bad function/);  
expect(fn).to.not.throw('good function');  
expect(fn).to.throw(ReferenceError, /bad function/);  
expect(fn).to.throw(err);  
expect(fn).to.not.throw(new RangeError('Out of range.'));  

// satisfy(method) 斷言目標通過一個真值測試
expect(1).to.satisfy(function(num) { return num > 0; })

三. mocha測試程式碼如何執行?
上面的add.test.js 編寫完成後,我們需要執行測試程式碼了,進入add.test.js程式碼的目錄後,執行如下命令可執行:

mocha add.test.js

如下結果:

$ mocha add.test.js


  加法函式的測試
    ✓ 1加1應該等於2


  1 passing (10ms)

如上所示,表示測試指令碼通過了測試,共用一個測試用例,耗時10毫秒。

mocha命令後面也可以指定多個檔案,如下命令:

mocha xx.test.js yy.test.js

3-1 把測試檔案放入test目錄下
mocha預設執行test子目錄裡面的測試指令碼,我們一般情況下,可以把測試指令碼放在test目錄下,然後進入對應的目錄,直接執行mocha命令即可:
請看demo2;
如下目錄頁面:

demo2
   |---- src
   |  |--- add.js
   |  |--- multiple.js
   |  |--- reduce.js
   |---- test
   |  |--- dir
   |  | |--- multiple.test.js 
   |  |
   |  |--- add.test.js

src/add.js 程式碼如下:

function add(x, y) {
  return x + y;
}
module.exports = add;

src/multiple.js程式碼如下:

function multiply(x, y) {
  return x * y;
}
module.exports = multiply;

src/reduce.js 程式碼如下:

function add(x, y) {
  return x - y;
}
module.exports = add;

test/add.test.js程式碼如下:

var add = require('../src/add.js');
var expect = require('chai').expect;

describe('加法函式的測試', function() {
  it('1 加 1 應該等於 2', function() {
    expect(add(1, 1)).to.be.equal(2);
  });

  it('任何數加0應該等於自身', function() {
    expect(add(1, 0)).to.be.equal(1);
  });
});

test/dir/multiple.test.js程式碼如下:

var multiply = require('../../src/multiply');
var expect = require('chai').expect;

describe('乘法函式的測試', function() {
  it('1 乘 1 應該等於 1', function() {
    expect(multiply(1, 1)).to.be.equal(1);
  });
})

當我在demo2專案目錄下,執行 mocha 命令後,執行如下:

$ mocha


  加法函式的測試
    ✓ 1 加 1 應該等於 2
    ✓ 任何數加0應該等於自身


  2 passing (10ms)

我們可以看到,test子目錄裡面的測試指令碼執行了,但是test目錄下還有dir這樣的目錄裡面的測試指令碼檔案並沒有執行,所以我們可以得出一個結論是,mocha
命令只會執行test第一層目錄下所有檔案,並不能執行巢狀目錄下的檔案。
為了執行所有巢狀目錄下的檔案,我們可以 mocha命令後面加一個引數 --recursive 引數,如下命令:

$ mocha --recursive


  加法函式的測試
    ✓ 1 加 1 應該等於 2
    ✓ 任何數加0應該等於自身

  乘法函式的測試
    ✓ 1 乘 1 應該等於 1


  3 passing (11ms)

四. 理解使用萬用字元
命令列中測試指令碼檔案,可能會有多個指令碼檔案需要被測試,這時候我們可以使用萬用字元,來做批量操作。
比如我們在 demo2下新建spec目錄,檔案目錄變成如下結構:

demo2
   |---- src
   |  |--- add.js
   |  |--- multiple.js
   |  |--- reduce.js
   |---- test
   |  |--- dir
   |  | |--- multiple.test.js 
   |  |
   |  |--- add.test.js
   |
   |----- spec
   |  |--- add.js
   |  |--- reduce.js

demo2/spec/add.js 程式碼如下:

var add = require('../src/add.js');
var expect = require('chai').expect;

describe('加法函式的測試', function() {
  it('1 加 1 應該等於 2', function() {
    expect(add(1, 1)).to.be.equal(2);
  });

  it('任何數加0應該等於自身', function() {
    expect(add(1, 0)).to.be.equal(1);
  });
});

demo2/spec/reduce.js程式碼如下:

var reduce = require('../src/reduce.js');
var expect = require('chai').expect;

describe('減法函式的測試', function() {
  it('2 減 1 應該等於 1', function() {
    expect(reduce(2, 1)).to.be.equal(1);
  });
});

我們可以執行如下命令,執行多個測試指令碼檔案:

mocha spec/{add,reduce}.js

命令效果如下:

$ mocha spec/{add,reduce}.js


  加法函式的測試
    ✓ 1 加 1 應該等於 2
    ✓ 任何數加0應該等於自身

  減法函式的測試
    ✓ 2 減 1 應該等於 1


  3 passing (11ms)

或者直接後面加*號,匹配所有的檔案,和js中的正則類似:如下命令:

$ mocha spec/*.js


  加法函式的測試
    ✓ 1 加 1 應該等於 2
    ✓ 任何數加0應該等於自身

  減法函式的測試
    ✓ 2 減 1 應該等於 1


  3 passing (10ms)

五. 命令列引數常用的有哪些?
5.1 --help
--help引數,用來檢視Mocha的所有命令列引數,如下命令所示:
mocha --help

5.2 --reporter
--reporter引數用來指定測試報告的格式,預設是spec格式。

$ mocha
# 等同於
$ mocha --reporter spec

我們可以使用 mocha --reporters 命令檢視所有內建的報告格式。如下命令:

$ mocha --reporters    

    dot - dot matrix
    doc - html documentation
    spec - hierarchical spec list
    json - single json object
    progress - progress bar
    list - spec-style listing
    tap - test-anything-protocol
    landing - unicode landing strip
    xunit - xunit reporter
    min - minimal reporter (great with --watch)
    json-stream - newline delimited json events
    markdown - markdown documentation (github flavour)
    nyan - nyan cat!

我們可以使用 mochawesome(http://adamgruber.github.io/mochawesome/) 模組,可以生成漂亮的HTML格式的報告。
首先我們需要安裝 mochawesome模組,如下命令列:

npm install --save-dev mochawesome
$ ../node_modules/.bin/mocha --reporter mochawesome


  加法函式的測試
    ✓ 1 加 1 應該等於 2
    ✓ 任何數加0應該等於自身


  2 passing (10ms)

[mochawesome] Report JSON saved to /Users/tugenhua/個人demo/vue1204/mocha/demo2/mochawesome-report/mochawesome.json

[mochawesome] Report HTML saved to /Users/tugenhua/個人demo/vue1204/mocha/demo2/mochawesome-report/mochawesome.html

因此會在demo2專案目錄下生成 mochawesome-report 檔案目錄,我們可以檢視 mochawesome/mochawesome.html檔案開啟看一下即可:

5.3 --watch
--watch 引數用來監聽指定的測試指令碼,只要測試指令碼有變化,就會自動執行mocha。我們在demo2目錄下,執行 mocha --watch命令,
然後修改指令碼檔案,可以看到如下:

$ mocha --watch

  加法函式的測試
    ✓ 1 加 1 應該等於 2
    ✓ 任何數加0應該等於自身


  2 passing (9ms)

  加法函式的測試
111 加 1 應該等於 2
11
    ✓ 任何數加0應該等於自身


  2 passing (2ms)

我在add.js 加了一句 console.log(11),上面可以看到也同樣重新執行了 mocha命令。

5.4 --bail
--bail引數指定只要有一個測試用例沒有通過,就停止執行後面的測試用例。
mocha --bail

5.5 --grep
--grep引數用於搜尋測試用例的名稱(即it塊的第一個引數),然後只執行到匹配的測試用例。
如下程式碼命令:

$ mocha                

  加法函式的測試
111 加 1 應該等於 2
11
    ✓ 任何數加0應該等於自身


  2 passing (10ms)


~/個人demo/vue1204/mocha/demo2 on  Dev_20171115_wealth!
$ mocha --grep "1 加 1"

  加法函式的測試
111 加 1 應該等於 2


  1 passing (12ms)

5.6 --invert
--invert參數列示只執行不符合條件的測試指令碼,必須與 --grep引數配合使用。
如下執行結果:

 

~/個人demo/vue1204/mocha/demo2 on  Dev_20171115_wealth! 
$ mocha                         

  加法函式的測試
111 加 1 應該等於 2
11
    ✓ 任何數加0應該等於自身


  2 passing (13ms)

~/個人demo/vue1204/mocha/demo2 on  Dev_20171115_wealth! 
$ mocha --grep "1 加 1" --invert

  加法函式的測試
11
    ✓ 任何數加0應該等於自身

  1 passing (10ms)

六. 配置檔案 mocha.opts
Mocha的測試指令碼檔案 允許放在test目錄下面,但是我們也可以在test目錄下新建一個mocha.opts檔案,把命令列寫在該裡面,還是看demo2目錄結構,
在test目錄下新建 mocha.opts檔案,如下目錄結構:

demo2
   |---- src
   |  |--- add.js
   |  |--- multiple.js
   |  |--- reduce.js
   |---- test
   |  |--- dir
   |  | |--- multiple.test.js 
   |  |
   |  |--- add.test.js
   |  |--- mocha.opts
   |
   |----- spec
   |  |--- add.js
   |  |--- reduce.js

mocha.opts檔案寫入如下命令:

--recursive
--reporter tap

然後執行mocha命令,就可以執行測試中的所有測試程式碼:

~/個人demo/vue1204/mocha/demo2 on  Dev_20171115_wealth! 
$ mocha
1..3
11
ok 1 加法函式的測試 1 加 1 應該等於 2
11
ok 2 加法函式的測試 任何數加0應該等於自身
ok 3 乘法函式的測試 1 乘 1 應該等於 1
# tests 3
# pass 3
# fail 0

當然如果測試用例不是存放在test子目錄下,可以在mocha.opts寫入如下內容:

server-tests
--recursive
--reporter tap 

上面程式碼指定允許 server-tests 目錄及其子目錄之中的測試指令碼。

七: ES6的測試;
如果測試指令碼是用ES6編寫的,那麼允許測試之前,需要先用babel轉碼,我們在test目錄下新建 es6.test.js檔案,先看目錄結構如下:

demo2
   |---- src
   |  |--- add.js
   |  |--- multiple.js
   |  |--- reduce.js
   |---- test
   |  |--- dir
   |  | |--- multiple.test.js 
   |  |
   |  |--- add.test.js
   |  |--- es6.test.js
   |  |--- mocha.opts
   |
   |----- spec
   |  |--- add.js
   |  |--- reduce.js

es6.test.js 程式碼如下:

import add from '../src/add.js';
import chai from 'chai';
let expect = chai.expect;

describe('加法函式的測試', function() {
  it('1 加 1 應該等於 2', function() {
    expect(add(1, 1)).to.be.equal(2);
  });

  it('任何數加0應該等於自身', function() {
    expect(add(1, 0)).to.be.equal(1);
  });
});

如果我們直接在demo2命令列中允許 mocha命令就會報錯,如下報錯:

$ mocha
/Users/tugenhua/個人demo/vue1204/mocha/demo2/test/es6.test.js:1
(function (exports, require, module, __filename, __dirname) { import add from '../src/add.js';
                                                              ^^^^^^

SyntaxError: Unexpected token import

因此我們需要ES6轉碼,需要安裝Babel。命令如下:

npm install babel-core babel-preset-es2015 --save-dev

然後我們需要在專案的目錄下,新建一個 .babelrc配置檔案。
新增如下程式碼如下:

{
  "presets": ['es2015']
}

最後,我們使用 --compilers 引數指定測試指令碼的轉碼器。

../node_modules/mocha/bin/mocha --compilers js:babel-core/register

如上命令程式碼,--compilers 引數後面是一個使用冒號分割的字串,冒號左邊是檔案的字尾名,右邊是用來處理這一類檔案的模組名,意思是說,先使用
babel-core/register模組,處理一下 .js檔案;如下命令列程式碼所示:

~/個人demo/vue1204/mocha/demo2 on  Dev_20171115_wealth! 
$ ../node_modules/mocha/bin/mocha --compilers js:babel-core/register
1..5
(node:55513) DeprecationWarning: "--compilers" will be removed in a future version of Mocha; see https://git.io/vdcSr for more info
11
ok 1 加法函式的測試 1 加 1 應該等於 2
11
ok 2 加法函式的測試 任何數加0應該等於自身
ok 3 乘法函式的測試 1 乘 1 應該等於 1
11
ok 4 加法函式的測試 1 加 1 應該等於 2
11
ok 5 加法函式的測試 任何數加0應該等於自身
# tests 5
# pass 5
# fail 0

注意點:Babel預設不會對 Iterator, Generator, Promise, Map, Set等全域性物件,以及一些全域性物件的方法(比如Object.assign)轉碼,
如果我們想要對這些物件轉碼,我們需要安裝 babel-polyfill.

npm install --save-dev babel-polyfill

最後,需要在我們的指令碼頭部加上 如下引入 babel-polyfill程式碼

import 'babel-polyfill'

八,非同步測試
先在mocha專案目錄下 新建檔案demo3,如下目錄結構:

demo3
  |---- timeout.test.js

timeout.test.js程式碼如下:

var expect = require('chai').expect;

describe('timeout.test.js - 超時測試', function() {
  it('測試應該 5000 毫秒後結束', function(done) {
    var x = true;
    var f = function() {
      x = false;
      expect(x).to.be.not.ok;
      done();
    };
    setTimeout(f, 4000);
  });
});

然後在demo3目錄下,執行命令列 mocha timeout.test.js, 執行如下:

~/個人demo/vue1204/mocha/demo3 on  Dev_20171115_wealth!
$ mocha timeout.test.js

  timeout.test.js - 超時測試
    1) 測試應該 5000 毫秒後結束


  0 passing (2s)
  1 failing

  1) timeout.test.js - 超時測試
       測試應該 5000 毫秒後結束:
     Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.

可以看到如上報錯 Timeout of 2000ms exceeded, 這是因為mocha預設每個測試用例最多執行2000毫秒,如果超過這個時間沒有返回結果,就會報錯,
所以我們在進行非同步操作的時候,需要額外指定timeout的時間的。因為非同步的操作是需要4000毫秒,所以我們指定5000毫秒就不會報錯了。
如下命令:

mocha --timeout 5000 timeout.test.js

如下執行結果:

~/個人demo/vue1204/mocha/demo3 on  Dev_20171115_wealth! 
$ mocha --timeout 5000 timeout.test.js

  timeout.test.js - 超時測試
    ✓ 測試應該 5000 毫秒後結束 (4008ms)

  1 passing (4s)

這樣就保證測試用例成功了。

Mocha內建對Promise的支援,允許直接返回Promise. 在demo3目錄下 新建 promise.test.js, 如下目錄結構:

demo3
  |---- timeout.test.js
  |---- promise.test.js

promise.test.js 程式碼如下:

var fetch = require('node-fetch');
var expect = require('chai').expect;

describe('promise非同步測試', function() {
  it('非同步請求應該返回一個物件', function() {
    return fetch("https://api.github.com")
      .then(function(res) {
        return res.json()
      }).then(function(json) {
        expect(json).to.be.an("object");
      })
  })
});

然後執行命令如下:

~/個人demo/vue1204/mocha/demo3 on  Dev_20171115_wealth!
$ mocha promise.test.js


  promise非同步測試
    ✓ 非同步請求應該返回一個物件 (1165ms)


  1 passing (1s)

如上可以看到也是可以成功的。

九:測試用例的鉤子
Mocha在describe塊之中,提供了測試用例的四個鉤子,before(), after(), beforeEach()和afterEach(),他們會在指定的時間內執行。

程式碼如下:

describe('hooks', function() {
  before(function(){
    // 在本區塊的所有測試用例之前執行
  });
  after(function(){
    // 在本區塊的所有測試用例之後執行
  });
  beforeEach(function(){
    // 在本區塊的每個測試用例之前執行
  });
  afterEach(function(){
    // 在本區塊的每個測試用例之後執行
  });
});

before(): 將會在所有測試用例執行之前執行,比如在之前插入資料等等操作。
after(): 會在所有測試執行之後執行,用於清理測試環境,回滾到清空資料狀態。
beforeEach(): 將會在每個測試用例執行之前執行,可用於測試測試需要準備相關資料的條件。
afterEach(): 將會在每個測試用例之後執行,可用於準備測試用例所需的後置條件。
請看如下demo,在來理解下 mocha的四個鉤子函式,
在專案的根目錄下 新建demo4,目錄結構如下:

demo4
  |---- src
  |  |-- hooks.js
  |---- test
  |  |--- hooks.test.js

hooks.js 程式碼如下:

// 儲存使用者物件
var saveUserObj = {};

// 定義使用者類
function User (name) {}

// 儲存使用者
User.save = function(name) {
  saveUserObj[name] = name;
} 

// 刪除使用者
User.delete = function(name) {
  delete saveUserObj[name];
}

// 檢查是否包含該使用者
User.contains = function(name) {
  return saveUserObj[name] !== null;
}
// 返回所有的資料
User.getUsers = function() {
  return saveUserObj;
}
module.exports = User;

hooks.test.js程式碼如下:

var should = require('should');
var User = require('../src/hooks.js');

// 描述User的行為
describe('描述User的行為', function(){
  // 執行所有測試之前,執行before函式,新增資料
  before(function(){
    User.save('kongzhi111');
    console.log(User.getUsers()); // 列印出{kongzhi111: 'kongzhi111'}
    console.log(111111111111111111111);
  });
  // 在執行每個測試前,執行beforeEach函式,新增資料
  beforeEach(function() {
    User.save('kongzhi222');
    console.log(User.getUsers());
    // 列印出 {kongzhi111: 'kongzhi111', kongzhi222: 'kongzhi222'}
    console.log(222222222222222222222222)
  })
  // 描述User.save的行為
  describe('描述User.save的行為', function() {
    // 儲存kongzhi333成功了
    it('儲存kongzhi333成功了', function() {
      User.save('kongzhi333');
      console.log(User.getUsers());
      // 列印出 {kongzhi111: 'kongzhi111', kongzhi222: 'kongzhi222', kongzhi333: 'kongzhi333'}
      console.log(33333333333333333333);
    })
  });
  // 描述User.contains的行為
  describe('描述User.contains的行為', function(){
    it('kongzhi111是存在的', function(){
      User.contains('kongzhi111').should.be.exactly(true);
    });
    it('kongzhi222是存在的', function(){
      User.contains('kongzhi222').should.be.exactly(true);
    });
    it('kongzhi333是存在的', function(){
      User.contains('kongzhi333').should.be.exactly(true);
    });
    it('kongzhi555是不存在', function(){
      User.contains('kongzhi555').should.be.exactly(true);
    });
  });
  // 在執行完每個測試後,清空資料
  afterEach(function() {
    User.delete('kongzhi222');
    console.log(User.getUsers());  // 列印 {kongzhi111: 'kongzhi111', kongzhi333: 'kongzhi333'}
    console.log(44444444444444444444444);
  });
  // 在執行完每個測試後,清空資料
  after(function() {
    User.delete('kongzhi111');
    console.log(User.getUsers()); // 列印 {kongzhi333: 'kongzhi333'}
    console.log(555555555555555555555555);
    User.delete('kongzhi333');
    console.log(User.getUsers()); // 列印 {}
  });
})

在demo4下 執行mocha,執行命令後 如下:

~/個人demo/vue1204/mocha/demo4 on  Dev_20171115_wealth!
$ mocha 

  描述User的行為
{ kongzhi111: 'kongzhi111' }
111111111111111110000
    描述User.save的行為
{ kongzhi111: 'kongzhi111', kongzhi222: 'kongzhi222' }
2.2222222222222222e+23
{ kongzhi111: 'kongzhi111',
  kongzhi222: 'kongzhi222',
  kongzhi333: 'kongzhi333' }
33333333333333330000
      ✓ 儲存kongzhi333成功了
{ kongzhi111: 'kongzhi111', kongzhi333: 'kongzhi333' }
4.4444444444444445e+22
    描述User.contains的行為
{ kongzhi111: 'kongzhi111',
  kongzhi333: 'kongzhi333',
  kongzhi222: 'kongzhi222' }
2.2222222222222222e+23
      ✓ kongzhi111是存在的
{ kongzhi111: 'kongzhi111', kongzhi333: 'kongzhi333' }
4.4444444444444445e+22
{ kongzhi111: 'kongzhi111',
  kongzhi333: 'kongzhi333',
  kongzhi222: 'kongzhi222' }
2.2222222222222222e+23
      ✓ kongzhi222是存在的
{ kongzhi111: 'kongzhi111', kongzhi333: 'kongzhi333' }
4.4444444444444445e+22
{ kongzhi111: 'kongzhi111',
  kongzhi333: 'kongzhi333',
  kongzhi222: 'kongzhi222' }
2.2222222222222222e+23
      ✓ kongzhi333是存在的
{ kongzhi111: 'kongzhi111', kongzhi333: 'kongzhi333' }
4.4444444444444445e+22
{ kongzhi111: 'kongzhi111',
  kongzhi333: 'kongzhi333',
  kongzhi222: 'kongzhi222' }
2.2222222222222222e+23
      ✓ kongzhi555是不存在
{ kongzhi111: 'kongzhi111', kongzhi333: 'kongzhi333' }
4.4444444444444445e+22
{ kongzhi333: 'kongzhi333' }
5.5555555555555555e+23
{}

可以看到如上測試結果後的執行,試著理解一下,應該可以理解mocha中的4各鉤子函式的含義了。

理解非同步鉤子函式

在demo4目錄下的test檔案下 新建 hooks-async.test.js 用於測試非同步的程式碼

demo4
  |---- src
  |  |-- hooks.js
  |---- test
  |  |--- hooks.test.js
  |  |--- hooks-async.test.js

hooks-async.test.js 程式碼如下:

var expect = require('chai').expect;
describe('非同步鉤子函式', function() {
  var foo = false;
  beforeEach(function(){
    setTimeout(function(){
      foo = true;
    }, 50)
  });
  it('非同步鉤子函式成功', function() {
    expect(foo).to.be.equal(true);
  })
});

執行結果如下:

非同步鉤子函式
       非同步鉤子函式成功:

      AssertionError: expected false to equal true
      + expected - actual

      -false
      +true

如上可以看到測試失敗,原因是因為setTimeout 是非同步的,在setTimeout執行完之前,it函式已經被執行了,所以foo當時資料還是false,
因此false不等於true了。

這時候 done引數出來了,在回撥函式存在時候,它會告訴mocha,你正在編寫一個非同步測試,會等到非同步測試完成的時候來呼叫done函式。
或者超過2秒後超時,如下程式碼就可以成功了;
hooks-async.test.js 程式碼如下:

var expect = require('chai').expect;
describe('非同步鉤子函式', function() {
  var foo = false;
  beforeEach(function(done){
    setTimeout(function(){
      foo = true;
      // complete the async beforeEach
      done();
    }, 50)
  });
  it('非同步鉤子函式成功', function() {
    expect(foo).to.be.equal(true);
  });
});

10. 理解測試用例的管理
一個指令碼中可能有很多測試用例,有時候,我們想只執行其中的幾個,這時候我們使用only方法。describe塊和it塊都允許呼叫only方法,
表示只執行某個測試套件或測試用例。
在專案中新建檔案demo5,結構如下:

demo5
  |---- src
  |  |-- add.js
  |---- test
  |  |--- add.test.js

add.test.js 程式碼如下:

var expect = require('chai').expect;
var add = require('../src/add.js');
it.only('1 加 1應該等於2', function() {
  expect(add(1, 1)).to.be.equal(2);
});

it('任何數加0應該等於自身', function() {
  expect(add(1, 0)).to.be.equal(1);
});

進入demo5目錄,執行mocha命令後,如下:

~/個人demo/vue1204/mocha/demo5 on  Dev_20171115_wealth!
$ mocha


  ✓ 1 加 1應該等於2

  1 passing (8ms)

可以看到只執行了 only方法。

十一:瀏覽器測試
除了在命令列執行,mocha還可以在瀏覽器下執行。
首先,使用 mocha init 命令在在指定的目錄生成初始化檔案。
mocha init demo6
在mocha目錄下 執行上面的命令後,會在demo6生成 index.html. mocha.css, mocha.js 和 tests.js 檔案。
index.html程式碼如下:

<!DOCTYPE html>
<html>
  <head>
    <title>Mocha</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="mocha.css" />
  </head>
  <body>
    <div id="mocha"></div>
    <script src="mocha.js"></script>
    <script>mocha.setup('bdd');</script>
    <script src="tests.js"></script>
    <script>
      mocha.run();
    </script>
  </body>
</html>

然後在demo6 下 新建一個 src/add.js 檔案
程式碼如下:

function add(x, y) {
  return x + y;
}

然後,把這個檔案,以及斷言庫chai.js,加入index.html。

程式碼如下:

<!DOCTYPE html>
<html>
  <head>
    <title>Mocha</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="mocha.css" />
  </head>
  <body>
    <div id="mocha"></div>

    <!-- 瀏覽器新加的測試程式碼 -->
    <script type="text/javascript" src="src/add.js"></script>
    <script type="text/javascript" src="http://chaijs.com/chai.js"></script>

    <script src="mocha.js"></script>
    <script>mocha.setup('bdd');</script>
    <script src="tests.js"></script>
    <script>
      mocha.run();
    </script>
  </body>
</html>

然後在 tests.js 新增如下程式碼:

var expect = chai.expect;

describe('加法函式的測試', function() {
  it('1 加 1 應該等於 2', function() {
    expect(add(1, 1)).to.be.equal(2);
  });

  it('任何數加0等於自身', function() {
    expect(add(1, 0)).to.be.equal(1);
    expect(add(0, 0)).to.be.equal(0);
  });
});

執行index.html 即可看到效果。

十二: 生成規格檔案
Mocha支援從測試用例生成規格檔案。
在mocha-demo專案內 新建demo7檔案,該目錄檔案存放如下檔案
如下目錄頁面:

demo7
   |---- src
   |  |--- add.js
   |  |--- multiple.js
   |---- test
   |  |--- dir
   |  | |--- multiple.test.js 
   |  |
   |  |--- add.test.js

進入demo7目錄,執行如下命令:

$ mocha --recursive -R markdown >spec.md

就會在該目錄下 生成 spec.md 檔案,-R markdown引數指定規格報告是markdown格式。
如果想生成HTML格式的報告spec.html,使用下面的命令。

$ mocha --recursive -R doc > spec.html

就會在該目錄下 生成 spec.html檔案。

git上檢視demo原始碼

相關文章