nodejs死亡筆記之測試神器Mocha

引言:如果一個項目沒有了測試,那它和鹹魚有什麼區別?

對於一個合格的項目來說,測試是必不可少的環節,那麼,如何對nodejs項目進行測試呢?本文將介紹一種測試神器–Mocha。

什麼是Mocha

mocha 是一個功能豐富的javascript測試框架,可以運行在nodejs和瀏覽器環境,使異步測試變得簡單有趣。mocha 串聯運行測試,允許靈活和精確地報告結果,同時映射未捕獲的異常用來糾正測試用例。

支持TDD/BDD 的 開發方式,結合 should.js/expect/chai/better-assert 斷言庫,能輕鬆構建各種風格的測試用例。

特點

  1. 簡單
  2. 靈活
  3. 有趣

安裝

通過npm全局安裝:

npm install -g mocha

掌握,從代碼開始

先不講理論,我們先來看一個使用Mocha做測試的例子:

//模塊依賴
var assert = require("assert");

//斷言條件
describe('Array', function(){
  describe('#indexOf()', function(){
    it('當值不存在時應該返回 -1', function(){
      assert.equal(-1, [1,2,3].indexOf(5));
      assert.equal(-1, [1,2,3].indexOf(0));
    });
  });
});

示例解析:測試用例首先需要引用斷言模塊,如上文中var assert = require(‘assert’);,代碼 assert.equal(-1, [1,2,3].indexOf(5)); 中使用的是assert.equal(actual, expected, [message]) 語法。作用等同於使用’==’進行相等判斷。actual爲實際值,expected 爲期望值。message爲返回的信息。

運行 Mocha:$ mocha

結果:

這裏寫圖片描述

上面的代碼放在test.js中,mocha默認識別test.js文件。

describe、it 函數我們將在mocha接口的方法解析中詳細講解。下面來解釋一下爲什麼要使用assert模塊。

assert(斷言)

斷言(assert)指的是對代碼行爲的預期。一個測試用例內部,包含一個或多個斷言(assert)。

斷言會返回一個布爾值,表示代碼行爲是否符合預期。測試用例之中,只要有一個斷言爲false,這個測試用例就會失敗,只有所有斷言都爲true,測試用例纔會通過。

比如上節示例中的:

assert.equal(-1, [1,2,3].indexOf(5));

assert.equal(-1, [1,2,3].indexOf(0));

實際值(-1)和期望值([1,2,3].indexOf(5))是一樣的,斷言爲true,所以這個測試用例成功了。

mocha 允許開發者使用任意的斷言庫,當這些斷言庫拋出了一個錯誤異常時,mocha將會捕獲並進行相應處理。這意味着你可以利用如 should.js斷言庫、 Node.js 常規的 assert 模塊或其它類似的斷言代碼庫。以下是衆所周知的適用於Node.js或瀏覽器的斷言庫:

  • should.js
  • expect.js
  • chai.js
  • better-assert
  • assert:nodejs原生模塊,在前文示例中我們有應用到。

由於Mocha自身不帶斷言庫,所以我們需要引入一個斷言庫。本文中使用的是chai.js,爲什麼會選這個斷言庫呢?

斷言庫Chai

Chai 是一個非常靈活的斷言庫,它可以讓你使用如下三種主要斷言方式的任何一種:

assert:

這是來自老派測試驅動開發的經典的assert方式。比如:

assert.equal(variable, "value");

expect:

這種鏈式的斷言方式在行爲驅動開發中最爲常見。比如:

expect(variable).to.equal("value");

should:

這也是在測試驅動開發中比較常用的方式之一。舉例:

variable.should.equal("value");

在本文中我們將使用expect的斷言方式。

expect 的斷言方式

expect 庫應用是非常廣泛的,它擁有很好的鏈式結構和仿自然語言的方法。通常寫同一個斷言會有幾個方法,比如

expect(response).to.be(true) 和 expect(response).equal(true)。

以下列舉了 expect 常用的主要方法:

  • ok :檢查是否爲真
  • true:檢查對象是否爲真
  • to.be、to:作爲連接兩個方法的鏈式方法
  • not:鏈接一個否定的斷言,如 expect(false).not.to.be(true)
  • a/an:檢查類型(也適用於數組類型)
  • include/contain:檢查數組或字符串是否包含某個元素
  • below/above:檢查是否大於或者小於某個限定值

在課程開始講解什麼是mocha的時說:mocha支持TDD/BDD 的 開發方式,結合 should.js、expect、chai、better-assert 斷言庫,能輕鬆構建各種風格的測試用例。這裏面有兩個知識點,一個是斷言庫,另一個是 TDD/BDD 。

斷言庫我們已經在前文中講解了,下面我們就開始講解TDD/BDD 。

PS: 更多的文檔可以看 http://chaijs.com/api/bdd/

BDD風格

mocha “接口” 系統允許開發者選擇自身喜愛的特定領域語言風格, mocha 提供 TDD(測試驅動開發)、BDD (行爲驅動開發) 和 exports 風格的接口。

BDD是“行爲驅動的開發”(Behavior-Driven Development)的簡稱,指的是寫出優秀測試的最佳實踐的總稱。

BDD認爲,不應該針對代碼的實現細節寫測試,而是要針對行爲寫測試。BDD測試的是行爲,即軟件應該怎樣運行。

BDD接口提供以下方法:

  • describe():測試套件
  • it():測試用例
  • before():所有測試用例的統一前置動作
  • after():所有測試用例的統一後置動作
  • beforeEach():每個測試用例的前置動作
  • afterEach():每個測試用例的後置動作

BDD的特徵就是使用describe()和it() 這兩個方法。我們接下來會詳細講解

before()、after()、beforeEach()和afterEach() 是爲測試做輔助的作用域,它們合起來組成了hook的概念。

descript()和it()

descript()

describe()方法接收兩個參數:第一個參數是一個字符串,表示測試套件的名字或標題,表示將要測試什麼。第二個參數是一個函數,用來實現這個測試套件。

上述中引出了一個概念:測試套件。那什麼是測試套件呢?

測試套件(test suite)指的是,一組針對軟件規格的某個方面的測試用例。也可以看作,對軟件的某個方面的描述(describe)。結構如下:

describe("A suite", function() {
  // ...
});

it()

要想理解it(),首先我們要知道什麼是測試用例?

測試用例(test case)指的是,針對軟件一個功能點的測試,是軟件測試的最基本單位。一組相關的測試用例,構成一個測試套件。

測試用例由it函數構成,它與describe函數一樣,接受兩個參數:第一個參數是字符串,表示測試用例的標題;第二個參數是函數,用來實現這個測試用例。

BDD風格用例:

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

describe('Array', function(){
  before(function(){
    console.log('在測試之前運行');
  });

  describe('#indexOf()', function(){
    it('當值不存在時應該返回 -1', function(){
      expect([1,2,3].indexOf(4)).to.equal(-1);
    });
  });
});

TDD風格

TDD(測試驅動開發)組織方式是使用測試集(suite)和測試(test)。

每個測試集都有 setup 和 teardown 函數。這些方法會在測試集中的測試執行前執行,它們的作用是爲了避免代碼重複以及最大限度使得測試之間相互獨立。

TDD接口:

  • suite:類似BDD中 describe()
  • test:類似BDD中 it()
  • setup:類似BDD中 before()
  • teardown:類似BDD中 after()
  • suiteSetup:類似BDD中 beforeEach()
  • suiteTeardown:類似BDD中 afterEach()

示例:

var assert = require("assert");

suite('Array', function(){
  setup(function(){
    console.log('測試執行前執行');
  });

  suite('#indexOf()', function(){
    test('當值不存在時應該返回 -1', function(){
      assert.equal(-1, [1,2,3].indexOf(4));
    });
  });
});

運行mocha:

mocha --ui tdd *.js (*表示的是文件名)

mocha 默認是使用 bdd 的接口,所以在這裏我們告訴mocha我們用的是tdd.

我們在前文中講到了 mocha 提供 TDD(測試驅動開發)、BDD (行爲驅動開發) 和 exports 風格的接口。其實還有 QUnit 和 require 風格的接口。

但是比較常用就是 BDD 和 TDD,mocha 默認的也是BDD,且 BDD 是 TDD 的一個專業版本,它指定了從業務需求的角度出發需要哪些單元測試。所以我們本課程中主要講解的是 mocha BDD風格的接口。

接下來我們將講解 BDD 中的 hook 。

BDD 中的 hook

hook 就是在測試流程的不同時段觸發,比如在整個測試流程之前,或在每個獨立測試之前等。

hook也可以理解爲是一些邏輯,通常表現爲一個函數或者一些聲明,當特定的事件觸發時 hook 才執行。

提供方法有:before()、beforeEach() after() 和 afterEach()。

方法解析:

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

  after(function() {
    //在執行本區塊的所有測試之後執行
  });

  beforeEach(function() {
    //在執行本區塊的每個測試之前都執行
  });

  afterEach(function() {
    //在執行本區塊的每個測試之後都執行
  });

  //測試用例

});

用過java中JUnit的人應該非常容易理解hock機制。

描述 hook

所有的 hook 都可以加上描述,這樣可以更好地定位到測試用例中的錯誤。如果 hook 函數指定了名稱,會在沒有描述時使用函數名,例如:

beforeEach(function() {
  //beforeEach hook
});

beforeEach(function needFun() {
  //beforeEach: namedFun
});

beforeEach('some description', function() {
  //beforeEach:some description
});

上述示例中 註釋的內容就是對 hook 的描述。

測試佔位

測試用例佔位只要添加一個沒有回調的 it() 方法即可:

var assert = require('assert');

describe('Array', function() {

  describe('#indexOf()', function() {
    //同步測試
    it('當值不存在時應該返回 -1', function() {
      assert.equal(-1, [1,2,3].indexOf(5));
      assert.equal(-1, [1,2,3].indexOf(0));
    });
  });

  describe('Array', function() {
    describe('#indexOf()', function() {
      //下面是一個掛起的測試
      it('當值不存在時應該返回 -1');
    });
  });
});

僅執行指定測試

僅執行指定測試的特性可以讓你通過添加 .only() 來指定唯一要執行的測試套件或測試用例:

describe('Array', function(){
  describe.only('#indexOf()', function(){
    ...
  })
})

或一個指定的測試用例:

describe('Array', function(){
  describe('#indexOf()', function(){
    it.only('當值不存在時應該返回 -1', function(){

    })

    it('當值不存在時應該返回 -1', function(){

    })
  })
})

注意只能出現一個 .only()

忽略指定測試

該特性和 .only() 非常相似,通過添加 .skip() 你可以告訴 Mocha 忽略的測試套件或者測試用例(可以有多個)。該操作使得這些操作處於掛起的狀態,這比使用註釋來的要好,因爲你可能會忘記把註釋給取消掉。

describe('Array', function(){
  describe.skip('#indexOf()', function(){
    ...
  })
})

或一個指定的測試用例:

describe('Array', function(){
  describe('#indexOf()', function(){
    it.skip('當值不存在時應該返回 -1', function(){

    })

    it('當值不存在時應該返回 -1', function(){

    })
  })
})

動態生成測試

由於mocha 可以使用 function.prototype.call 和function 表達式定義測試套件和測試用例,所以可以動態生成測試用例。

var assert = require('assert');

function add() {
  return Array.prototype.slice.call(arguments).reduce(function(prev, curr) {
    return prev + curr;
  }, 0);
}

describe('add()', function() {
  var tests = [
    {args: [1, 2],       expected: 3},
    {args: [1, 2, 3],    expected: 6},
    {args: [1, 2, 3, 4], expected: 10}
  ];

  tests.forEach(function(test) {
    it('correctly adds ' + test.args.length + ' args', function() {
      var res = add.apply(null, test.args);
      assert.equal(res, test.expected);
    });
  });
});

Mocha的基本知識我們就講完了,還是那句話,沒有實際項目的博客不是好博客,最後,用一個簡單的測試demo來結束本篇文章。

一隻野生Demo

superagent
在用Node做Web開發的時候,模擬HTTP請求時必不可少的。這也就引出了superagent這個模塊,它是一個模擬http請求的庫。它作用是簡化發起請求的庫。

項目的package.json 代碼如下:

{
  "name": "mocha-test",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node app.js",
    "test": "mocha test"
  },
  "devDependencies": {
    "superagent": "1.4.0",
    "chai": "3.4.0"
  }
}

首先我們創建一個app.js文件。內容如下:

var http = require('http'),
    PORT = 3000;

function onRequest(request, response) {
    console.log("Request received.");
    response.writeHead(200, {"Content-Type": "text/plain"});
    response.write("Hello World");
    response.end();
}

//我們把啓動服務和關閉服務分別進行了封裝並且對外進行了暴露。
var server = http.createServer(onRequest);
var boot = function () {
    server.listen(PORT, function () {
        console.info('Express server listening on port ' + PORT);
    });
};
var shutdown = function () {
    server.close();
};
if (require.main === module) {
    boot();
} else {
    console.info('Running app as a module');
    exports.boot = boot;
    exports.shutdown = shutdown;
}

描述:爲了讓項目儘可能的簡單,我們沒有用到任何的框架。只是創建了一個http服務器監聽了80端口。

測試代碼test.js,測試用例前需要啓動服務器,結束後關閉服務。這個時候就用到了前面暴露的 boot() 和 shutdown() 方法:

var boot = require('./app').boot,
    shutdown = require('./app').shutdown,
    request = require('superagent'),
    expect = require('chai').expect;

describe('server', function () {
    before(function () {
        boot();
    });
    describe('index', function () {
        it('should respond to GET', function (done) {
            request
                .get('http://localhost:3000')
                .end(function (err, res) {
                    expect(res.status).to.equal(200);
                    done();
                });
        });
    });
    after(function () {
        shutdown();
    });
});

最後,運行測試代碼,運行命令:mocha

結果:
這裏寫圖片描述

到這裏,這篇文章就結束了,歡迎大家和我一起學習nodejs。測試代碼依舊放在github上:https://github.com/CleverFan/nodejsStudy/tree/master/nodeTest/day02_mocha

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章