[Node.js] Express的測試覆蓋率


原文地址:http://www.moye.me/2014/12/03/express_coverage/

 

引子

有群友問到Express怎么做 單元測試/覆蓋率測試,這是上篇所遺漏的,特此補上

Express Web測試

做 Express Web 測試首先要面對的問題是在哪端進行測試:

  • 客戶端的請求響應測試是黑盒,需要預啟動站點,且無法附加覆蓋率測試
  • 服務端的單元測試需要 Mock ,可附加覆蓋率測試

我們需要對Express的路由做覆蓋率測試,顯然,我們會選擇在服務端進行測試。這意味着:每個case需要訪問的express application 不是這樣預先啟動的:

var express = require('express');
var app = express();
//some router code...
app.listen(3000);

我們需要一個工具能創建啟動express application,並 Mock 對它的請求,只有這樣,測試框架才能檢測到路由方法內部代碼執行的路徑和覆蓋率。

這里,我們引入supertest 做為 mock 工具。

SuperTest

SuperTest 是TJ大神的又一款作品:基於SuperAgent ,提供對HTTP測試的高度抽象。所謂高度抽象的意思是:能嵌入各類測試框架,提供語義良好的斷言。

來看段 SuperTest結合 Mocha的代碼:

var app = require('../app');
var request = require('supertest');

describe('router testing', function () {
    it('site root response', function (done) {
        request(app)
            .get('/')
            .expect('Content-Type', 'text/html; charset=utf-8')
            .expect(200)
            .end(function(err, res){
                if (err) throw err;
                done();
            });
    });

簡單易懂,重點是它驅動了express。

測試覆蓋率

代碼覆蓋(Code coverage)是軟件測試中的一種度量,描述程式中源代碼被測試的比例和程度,所得比例稱為代碼覆蓋率。

以下是幾個覆蓋率指標:

  • 函數覆蓋率(Function coverage):調用到程式中的每一個Function嗎?
  • 行覆蓋率(Line coverage):執行到程序中的每一行了嗎?
  • 語句覆蓋率(Statement coverage):若用控制流圖表示程序,執行到控制流圖中的每一個節點了嗎?
  • 分支覆蓋率(Branches coverage):若用控制流圖表示程式,執行到控制流圖中的每一條邊嗎?例如控制結構中所有IF指令都有執行到邏輯運算式成立及不成立的情形嗎?
  • 條件覆蓋率(Condition coverage):也稱為謂詞覆蓋(predicate coverage),每一個邏輯運算式中的每一個條件(無法再分解的邏輯運算式)是否都有執行到成立及不成立的情形嗎?

對指標的偏好可說是見仁見智,比如大名鼎鼎的 coveralls.io 就以行覆蓋率(Line coverage) 作為給項目頒發badge的首選指標。

我們需要的,是一個能根據測試用例得出覆蓋率指標的工具:

Istanbul

istanbul 就是這樣一個工具,能產生 Statements/Lines/Functions/Branches 等指標報表,並以各種格式導出。

值得稱道的是,istanbul 能和 Mocha 很好的集成,如:把測試用例統一放置在 /test下,要對它們進行測試並生成覆蓋率報表,可以在 package.json 中添加這樣的配置:

"scripts": {
    "test": "mocha --recursive --timeout 500000 test/ --bail",
    "test-cov": "node node_modules/istanbul-harmony/lib/cli.js cover ./node_modules/mocha/bin/_mocha  -- --timeout 500000 --recursive test/ --bail"
}

只需要進行測試時,在項目目錄下使用命令:

npm test

需要進行測試並追加覆蓋率報表時,在項目目錄下使用命令:

npm run-script test-cov

在測試部分完成后,會得到如下報表信息(在項目 /coverage 目錄下,會生成lcov.info 等覆蓋率數據文件:

istanbul_coverage

實例

mock 工具有了, 測試框架和覆蓋率工具也有了,就差實戰了。下面舉個粟子看看怎么做 Express 的覆蓋率測試:

  1. 全局安裝 mocha ,istanbul 及 express
    npm install -g mocha
    npm install -g istanbul
    npm install -g express
  2. 生成一個express 站點:
    express -e express-coverage
  3. 修改package.json如下,並使用npm install 安裝需要的包:
    {
      "name": "express-coverage",
      "version": "0.0.1",
      "scripts": {
        "test": "mocha test/ --bail",
        "test-cov": "node node_modules/istanbul-harmony/lib/cli.js cover ./node_modules/mocha/bin/_mocha  -- test/"
      },
      "dependencies": {
        "express": "~4.9.0",
        "body-parser": "~1.8.1",
        "cookie-parser": "~1.3.3",
        "morgan": "~1.3.0",
        "serve-favicon": "~2.1.3",
        "debug": "~2.0.0",
        "ejs": "~0.8.5",
        "istanbul-harmony": "*",
        "should": "*",
        "mocha": "*",
        "mocha-lcov-reporter": "*",
        "supertest" : "*"
      }
    }
  4. 把自帶的routes/index.js,、bin/www 刪除;改寫routes/users.js:
    var express = require('express');
    var router = express.Router();
    router.get('/', function (req, res) {
        var msg = 'no user';
        res.send(msg);
    });
    router.get('/:id', function (req, res) {
        var msg = 'user: ' + req.params.id;
        res.send(msg);
    });
    module.exports = router;
  5. 在項目下新建一個test目錄,放置一個 router.js,並編寫用例:
    var should = require('should');
    var app = require('../app');
    var request = require('supertest');
    describe('router testing', function () {
        it('users have not id response', function (done) {
            request(app)
                .get('/users')
                .expect('Content-Type', 'text/html; charset=utf-8')
                .expect(200)
                .end(function(err, res){
                    if (err) throw err;
                    should.exist(res.text);
                    done();
                });
        });
    
        it('users have id response', function (done) {
            request(app)
                .get('/users/1/')
                .expect('Content-Type', 'text/html; charset=utf-8')
                .expect(200)
                .end(function(err, res){
                    if (err) throw err;
                    should.exists(res.text);
                    done();
                });
        });
    });
  6. 輸入命令npm run-script test-cov 得到覆蓋率報表:
    testing_coverage_first
  7. 指標有點低是不是,因為app里有分支和代碼是用例沒跑到的: 404和500處理代碼(這些是express-generator的生成代碼:
    app.use(function(req, res, next) {
        var err = new Error('Not Found');
        err.status = 404;
        next(err);
    });
    app.use(function(err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
            message: err.message,
            error: {}
        });
    });
  8. 在 router.js 原有場景中,添加一個用例,加上對404代碼行的觸發:
    it('404 response', function (done) {
        request(app)
            .get('/non/')
            .expect(404)
            .end(function(err, res){
                if (err) throw err;
                done();
            });
    });
  9. 再次輸入命令npm run-script test-cov 查看覆蓋率報表,我們能看到進步 :)
    testing_coverage_second

后記

badge找到合適的 Mock工具和測試框架並進行整合,Web測試及覆蓋率報表獲取的思路大抵如此。關於測試框架的各種參數組合和花樣玩法,還有很多有意思的功能(比如和 Travis-CICoveralls.io 等公共服務集成,在倉庫上展示項目狀態徽章),本文不再贅述,有興趣的可加node學習交流群一起探討。

 

更多文章請移步我的blog新地址: http://www.moye.me/  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM