原文地址: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 等覆蓋率數據文件:
實例
mock 工具有了, 測試框架和覆蓋率工具也有了,就差實戰了。下面舉個粟子看看怎么做 Express 的覆蓋率測試:
- 全局安裝 mocha ,istanbul 及 express
npm install -g mocha
npm install -g istanbul
npm install -g express
- 生成一個express 站點:
express -e express-coverage
- 修改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" : "*" } }
- 把自帶的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;
- 在項目下新建一個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(); }); }); });
- 輸入命令
npm run-script test-cov
得到覆蓋率報表: - 指標有點低是不是,因為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: {} }); });
- 在 router.js 原有場景中,添加一個用例,加上對404代碼行的觸發:
it('404 response', function (done) { request(app) .get('/non/') .expect(404) .end(function(err, res){ if (err) throw err; done(); }); });
- 再次輸入命令
npm run-script test-cov
查看覆蓋率報表,我們能看到進步 :)
后記
找到合適的 Mock工具和測試框架並進行整合,Web測試及覆蓋率報表獲取的思路大抵如此。關於測試框架的各種參數組合和花樣玩法,還有很多有意思的功能(比如和 Travis-CI、Coveralls.io 等公共服務集成,在倉庫上展示項目狀態徽章),本文不再贅述,有興趣的可加node學習交流群一起探討。
更多文章請移步我的blog新地址: http://www.moye.me/