在團隊合做中,你寫好了一個函數,供隊友使用,跑去跟你的隊友說,你傳個A值進去,他就會返回B結果了。過了一會,你隊友跑過來講,我傳個A值卻返回C結果,怎麼回事?你丫的有沒有測試過啊?html
你們一塊兒寫個項目,不免會有我要寫的函數裏面依賴別人的函數,可是這個函數到底值不值得信賴?單元測試是衡量代碼質量的一重要標準,縱觀Github的受歡迎項目,都是有test文件夾,而且buliding-pass的。若是你也爲社區貢獻過module,想更多人使用的話,加上單元測試吧,讓你的module值得別人信賴。html5
要在Nodejs中寫單元測試的話,你須要知道用什麼測試框架,怎麼測試異步函數,怎麼測試私有方法,怎麼模擬測試環境,怎麼測試依賴HTTP協議的web應用,須要瞭解TDD和BDD,還有須要提供測試的覆蓋率。java
Nodejs的測試框架還用說?你們都在用,Mocha。node
Mocha 是一個功能豐富的Javascript測試框架,它能運行在Node.js和瀏覽器中,支持BDD、TDD、QUnit、Exports式的測試,本文主要示例是使用更接近與思考方式的BDD,若是瞭解更多能夠訪問Mocha的官網git
Mocha的BDD接口有:github
describe()
it()
before()
after()
beforeEach()
afterEach()
npm install mocha -g
web
模塊具有limit方法,輸入一個數值,小於0的時候返回0,其他正常返回shell
exports.limit = function (num) { if (num < 0) { return 0; } return num; };
lib
,存放模塊代碼的地方test
,存放單元測試代碼的地方index.js
,向外導出模塊的地方package.json
,包描述文件var lib = require('index'); describe('module', function () { describe('limit', function () { it('limit should success', function () { lib.limit(10); }); }); });
在當前目錄下執行mocha
:express
$ mocha
․
✔ 1 test complete (2ms)
上面的代碼只是運行了代碼,並無對結果進行檢查,這時候就要用到斷言庫了,Node.js中經常使用的斷言庫有:npm
使用should
庫爲測試用例加上斷言
it('limit should success', function () { lib.limit(10).should.be.equal(10); });
需求變動啦: limit
這個方法還要求返回值大於100時返回100。
針對需求重構代碼以後,正是測試用例的價值所在了,
它能確保你的改動對原有成果沒有形成破壞。
可是,你要多作的一些工做的是,須要爲新的需求編寫新的測試代碼。
lib庫中新增async函數:
exports.async = function (callback) { setTimeout(function () { callback(10); }, 10); };
測試異步代碼:
describe('async', function () { it('async', function (done) { lib.async(function (result) { done(); }); }); });
使用should提供的Promise斷言接口:
finally
| eventually
fulfilled
fulfilledWith
rejected
rejectedWith
then
測試代碼
describe('should', function () { describe('#Promise', function () { it('should.reject', function () { (new Promise(function (resolve, reject) { reject(new Error('wrong')); })).should.be.rejectedWith('wrong'); }); it('should.fulfilled', function () { (new Promise(function (resolve, reject) { resolve({username: 'jc', age: 18, gender: 'male'}) })).should.be.fulfilled().then(function (it) { it.should.have.property('username', 'jc'); }) }); }); });
Mocha的超時設定默認是2s,若是執行的測試超過2s的話,就會報timeout錯誤。
能夠主動修改超時時間,有兩種方法。
mocha -t 10000
describe('async', function () { this.timeout(10000); it('async', function (done) { lib.async(function (result) { done(); }); }); });
這樣的話async
執行時間不超過10s,就不會報錯timeout錯誤了。
異常應該怎麼測試,如今有getContent
方法,他會讀取指定文件的內容,可是不必定會成功,會拋出異常。
exports.getContent = function (filename, callback) { fs.readFile(filename, 'utf-8', callback); };
這時候就應該模擬(mock)錯誤環境了
describe("getContent", function () { var _readFile; before(function () { _readFile = fs.readFile; fs.readFile = function (filename, encoding, callback) { process.nextTick(function () { callback(new Error("mock readFile error")); }); }; }); // it(); after(function () { // 用完以後記得還原。不然影響其餘case fs.readFile = _readFile; }) });
Mock小模塊:muk
,略微優美的寫法:
var fs = require('fs'); var muk = require('muk'); before(function () { muk(fs, 'readFile', function(path, encoding, callback) { process.nextTick(function () { callback(new Error("mock readFile error")); }); }); }); // it(); after(function () { muk.restore(); });
針對一些內部的方法,沒有經過exports暴露出來,怎麼測試它?
function _adding(num1, num2) { return num1 + num2; }
模塊:rewire
it('limit should return success', function () { var lib = rewire('../lib/index.js'); var litmit = lib.__get__('limit'); litmit(10); });
在開發Web項目的時候,要測試某一個API,如:/user
,到底怎麼編寫測試用例呢?
使用:supertest
var express = require("express"); var request = require("supertest"); var app = express(); // 定義路由 app.get('/user', function(req, res){ res.send(200, { name: 'jerryc' }); }); describe('GET /user', function(){ it('respond with json', function(done){ request(app) .get('/user') .set('Accept', 'application/json') .expect('Content-Type', /json/) .expect(200) .end(function (err, res) { if (err){ done(err); } res.body.name.should.be.eql('jerryc'); done(); }) }); });
測試的時候,咱們經常關心,是否全部代碼都測試到了。
這個指標就叫作"代碼覆蓋率"(code coverage)。它有四個測量維度。
- 行覆蓋率(line coverage):是否每一行都執行了?
- 函數覆蓋率(function coverage):是否每一個函數都調用了?
- 分支覆蓋率(branch coverage):是否每一個if代碼塊都執行了?
- 語句覆蓋率(statement coverage):是否每一個語句都執行了?
Istanbul 是 JavaScript 程序的代碼覆蓋率工具。
$ npm install -g istanbul
在編寫過以上的測試用例以後,執行命令:
istanbul cover _mocha
就能獲得覆蓋率:
JerryC% istanbul cover _mocha module limit ✓ limit should success async ✓ async getContent ✓ getContent add ✓ add should #Promise ✓ should.reject ✓ should fulfilled 6 passing (32ms) ================== Coverage summary ====================== Statements : 100% ( 10/10 ) Branches : 100% ( 2/2 ) Functions : 100% ( 5/5 ) Lines : 100% ( 10/10 ) ==========================================================
這條命令同時還生成了一個 coverage 子目錄,其中的 coverage.json 文件包含覆蓋率的原始數據,coverage/lcov-report 是能夠在瀏覽器打開的覆蓋率報告,其中有詳細信息,到底哪些代碼沒有覆蓋到。
上面命令中,istanbul cover
命令後面跟的是 _mocha
命令,前面的下劃線是不能省略的。
由於,mocha 和 _mocha 是兩個不一樣的命令,前者會新建一個進程執行測試,然後者是在當前進程(即 istanbul 所在的進程)執行測試,只有這樣, istanbul 纔會捕捉到覆蓋率數據。其餘測試框架也是如此,必須在同一個進程執行測試。
若是要向 mocha 傳入參數,能夠寫成下面的樣子。
$ istanbul cover _mocha -- tests/test.sqrt.js -R spec
上面命令中,兩根連詞線後面的部分,都會被看成參數傳入 Mocha 。若是不加那兩根連詞線,它們就會被看成 istanbul 的參數(參考連接1,2)。
TESTS = test/*.test.js REPORTER = spec TIMEOUT = 10000 JSCOVERAGE = ./node_modules/jscover/bin/jscover test: @NODE_ENV=test ./node_modules/mocha/bin/mocha -R $(REPORTER) -t $(TIMEOUT) $(TESTS) test-cov: lib-cov @LIB_COV=1 $(MAKE) test REPORTER=dot @LIB_COV=1 $(MAKE) test REPORTER=html-cov > coverage.html lib-cov: @rm -rf ./lib-cov @$(JSCOVERAGE) lib lib-cov .PHONY: test test-cov lib-cov make test make test-cov
用項目自身的jscover和mocha,避免版本衝突和混亂
npm test
命令注意:Travis會將未描述的項目看成Ruby項目。因此須要在根目錄下加入.travis.yml
文件。內容以下:
language: node_js
node_js:
- "0.12"
Travis-cli還會對項目頒發標籤,
若是項目經過全部測試,就會build-passing,
若是項目沒有經過全部測試,就會build-failing
實施單元測試的時候, 若是沒有一份通過實踐證實的詳細規範, 很難掌握測試的 "度", 範圍過小施展不開, 太大又侵犯 "別人的" 地盤. 上帝的歸上帝, 凱撒的歸凱撒, 給單元測試念念緊箍咒不見得是件壞事, 反而更有利於發揮單元測試的威力, 爲代碼重構和提升代碼質量提供動力.
這份文檔來自 Geotechnical, 是一份很是可貴的經驗準則. 你徹底能夠以這份準則做爲模板, 結合所在團隊的經驗, 整理出一分內部單元測試準則.
最後,介紹一個庫:faker
他是一個能僞造用戶數據的庫,包括用戶常包含的屬性:我的信息、頭像、地址等等。
是一個開發初期,模擬用戶數據的絕佳好庫。
支持Node.js和瀏覽器端。