Mocha
Mocha 是一個 JavaScript 的測試框架,目的是用來管理測試的程式碼。在這裡指定介面(Interface)為 BDD。若使用 BDD 則提供 describe()、it()、before()、after()、beforeEach() 與 afterEach() 方法。
語法說明
語法範例
- describe('hooks', function() {
- // 測試區塊
- before(function() {
- // 在所有測試開始前會執行的程式碼區塊
- });
- after(function() {
- // 在所有測試結束後會執行的程式碼區塊
- });
- beforeEach(function() {
- // 在每個 Test Case 開始前執行的程式碼區塊
- });
- afterEach(function() {
- // 在每個 Test Case 結束後執行的程式碼區塊
- });
- // 撰寫個別 Test Case
- it('should ...', function() {
- // 執行 Test Case
- });
- });
Chai 提供 BDD 語法測試用的斷言庫(Assertion Library)。斷言庫是一種判斷工具,驗證執行結果是否符合預期,若實際結果和預測不同,就是測到 bug 了。以下分 Assert 和 Expect / Should 說明。
Assert
assert(expression, message):測試這個項目的 expression 'foo' === 'bar' 是否為真,若為假則顯示錯誤訊息 message:
- test/test_assert1.js
- var { assert } = require("chai")
- describe('AssertTest', function() {
- var foo = 'Hello';
- var bar = "World";
- it('should be equal', function() {
- assert(foo === bar, 'foo is not bar');
- });
- });
Expect / Should
預期 3 等於(===)2。這是使用可串連的 getters 來完成斷言。這些可串聯的 getters 有 to、is、have 等。它很像英文,用很口語的方式做判斷:
- test/test_expect1.js
- const { expect } = require('chai')
- describe('ExpectTest', function(){
- it('should be equal', function(){
- expect(3).to.equal(2);
- })
- });
測試 add(兩數相加)和 sub(兩數相減)功能. 首先來看被測試模組:
- op.js
- // op.js module
- function _add(x, y) {
- return x + y;
- }
- function _sub(x, y) {
- return x - y;
- }
- module.exports = {
- add: _add,
- sub: _sub,
- };
- test/test_op.js
- const { expect } = require('chai')
- const testModule = require('../op');
- describe('Test add', () => {
- it('1 + 2 = 3', () => {
- expect(testModule.add(1, 2)).to.equal(3);
- });
- it('3 + 4 = 7', () => {
- expect(testModule.add(3, 4)).to.equal(7);
- });
- });
- describe('Test sub', () => {
- it('1 - 2 = -1', () => {
- expect(testModule.sub(1, 2)).to.equal(-1);
- });
- it('11 - 4 = 7', () => {
- expect(testModule.sub(11, 4)).to.equal(7);
- });
- });
比較 Assert、Expect、Should 的差異
三者基本上都可完成相同工作,除了
Sinon
用來產生 Test Double(測試替身),可當成假資料來看,分為 Spy、Stub 和 Mock。
Spy (Spies - Sinon.JS)
對 function call 蒐集資訊,便於對測試結果做驗證。sinon.spy() 會回傳一個 Spy 物件,這個 Spy 物件像蜜糖般包裹於原 function 外,讓我們可以像 function 一樣呼叫。而這個 Spy 物件的 property 會協助蒐集由 function call 得到的資訊,例如:取得第一次呼叫所輸入的參數、該 function 被呼叫的次數。Spy 是三者最簡單的部份,並且 Stub 和 Mock 是建構於 Spy 之上的。
底下為待測程式:
- cmp.js
- function _comparePeople(people1, people2, callback){
- let rst = people1.localeCompare(people2)
- callback(null, rst)
- }
- module.exports = {
- comparePeople: _comparePeople
- }
- test/test_spy.js
- const expect = require('chai').expect;
- const sinon = require('sinon');
- const testModule = require('../cmp');
- describe('Test comparePeople', function(){
- it('should call the callback function', function(){
- var nameList = ['Nina', 'Ricky'];
- var callback = sinon.spy();
- testModule.comparePeople(nameList[0], nameList[0], callback);
- console.log(callback.callCount); // The calling time
- expect(callback.callCount).to.equal(1)
- });
- });
Stub (Stubs - Sinon.JS)
取代 function。與 Spy 不同的是,Spy 依然會執行真的 function,但 Stub 並不會。適用於 Ajax 和 Timer。
一個使用 stub 的測試範例如下:
- test/test_stub1.js
- const { expect } = require('chai');
- const sinon = require('sinon');
- const uuid = require('node-uuid');
- describe('stub example', function(){
- it('check length of uuid', ()=> {
- var stub = sinon.stub(uuid, 'v4');
- var mockId = stub.v4();
- expect(mockId.length).to.equal(36);
- uuid.v4.restore();
- });
- });
更多的 stub 使用範例如下:
- test/test_stub2.js
- const sinon = require('sinon');
- const { expect } = require('chai')
- class A{
- constructor(){}
- add(a, b){
- let sum = a + b;
- return sum
- }
- }
- describe('test A', ()=>{
- it('demo usage of stub API', function(){
- const a = new A();
- // 1) API:callsFake
- sinon.stub(a, 'add').callsFake(function(a, b){
- return 0
- })
- var sum = a.add(1, 2)
- expect(a.add(1, 2)).to.equal(0)
- // 2) Restore add
- a.add.restore()
- expect(a.add(1, 2)).to.equal(3)
- // 3) API:withArgs,callThrough
- var obj = {};
- obj.sum = function sum(a, b){ return a + b; };
- sinon.stub(obj, 'sum')
- obj.sum.withArgs(2, 2).callsFake(function foo(){ return 'bar'; });
- obj.sum.callThrough();
- expect(obj.sum(2, 2)).to.equal('bar')
- expect(obj.sum(1, 2)).to.equal(3)
- obj.sum.restore()
- expect(obj.sum(2, 2)).to.equal(4)
- });
- });
Mock (Mocks - Sinon.JS)
取代整個物件,包含完整實作細節。一個簡單測試範例如下:
- test/test_mock.js
- const { expect } = require('chai')
- const sinon = require('sinon')
- var opts = {
- call: function(msg){ console.log(msg); }
- };
- describe('test Mock', ()=>{
- it('should pass Hello World to run call()', function(){
- // 1) Creates a mock for the provided object.
- var mock = sinon.mock(opts);
- mock
- .expects('call') // 2.1 ) Overrides obj.call with a mock function and returns it.
- .once() // 2.2) Expect the method to be called exactly once.
- .withExactArgs('Hello World'); // 2.3) Expect the method to be called with the provided arguments and no others.
- // 3) Launch testing
- opts.call('Hello World');
- // 4) Restores all mocked methods.
- mock.restore();
- // 5) Verifies all expectations on the mock.
- mock.verify();
- });
- });
The repo for above sample code.
Supplement
* NodeJS 文章收集 - A quick and complete guide to Mocha testing
* Chai Assertion Library
* FAQ - Why is the constructor invoked despite calling createStubInstance?
沒有留言:
張貼留言