在Nodejs中貫徹單元測試
在團隊合作中,你寫好了一個函式,供隊友使用,跑去跟你的隊友說,你傳個A值進去,他就會返回B結果了。過了一會,你隊友跑過來說,我傳個A值卻返回C結果,怎麼回事?你丫的有沒有測試過啊?
大家一起寫個專案,難免會有我要寫的函式裡面依賴別人的函式,但是這個函式到底值不值得信賴?單元測試是衡量程式碼質量的一重要標準,縱觀Github的受歡迎專案,都是有test資料夾,並且buliding-pass的。如果你也為社群貢獻過module,想更多人使用的話,加上單元測試吧,讓你的module值得別人信賴。
要在Nodejs中寫單元測試的話,你需要知道用什麼測試框架,怎麼測試非同步函式,怎麼測試私有方法,怎麼模擬測試環境,怎麼測試依賴HTTP協議的web應用,需要了解TDD和BDD,還有需要提供測試的覆蓋率。
本文的示例程式碼會備份到 Github :
unittest-demo
目錄
- 測試框架
- 斷言庫
- 需求變更
- 非同步測試
- 異常測試
- 測試私有方法
- 測試Web應用
- 覆蓋率
- 使用Makefile把測試串起來
- 持續整合,Travis-cli
- 一些觀點
- 彩蛋
- 整理
測試框架
Nodejs的測試框架還用說?大家都在用,Mocha。
Mocha 是一個功能豐富的Javascript測試框架,它能執行在Node.js和瀏覽器中,支援BDD、TDD、QUnit、Exports式的測試,本文主要示例是使用更接近與思考方式的BDD,如果瞭解更多可以訪問Mocha的官網
測試介面
Mocha的BDD介面有:
describe()
it()
before()
after()
beforeEach()
afterEach()
安裝
npm install mocha -g
編寫一個穩定可靠的模組
模組具備limit方法,輸入一個數值,小於0的時候返回0,其餘正常返回
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
:
$ mocha
․
1 test complete (2ms)
斷言庫
上面的程式碼只是執行了程式碼,並沒有對結果進行檢查,這時候就要用到斷言庫了,Node.js中常用的斷言庫有:
- should.js
- expect.js
- chai
加上斷言
使用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();
});
});
});
測試Promise
使用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
API式
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)錯誤環境了
簡單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庫
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匯出方法
模組:rewire
it(`limit should return success`, function () {
var lib = rewire(`../lib/index.js`);
var litmit = lib.__get__(`limit`);
litmit(10);
});
測試Web應用
在開發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)。
使用Makefile串起專案
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,避免版本衝突和混亂
持續整合,Travis-cli
-
Travis-ci
- 繫結Github帳號
- 在Github倉庫的Admin開啟Services hook
- 開啟Travis
- 每次push將會hook觸發執行
npm test
命令
注意:Travis會將未描述的專案當作Ruby專案。所以需要在根目錄下加入.travis.yml
檔案。內容如下:
language: node_js
node_js:
- "0.12"
Travis-cli還會對專案頒發標籤,
or
如果專案通過所有測試,就會build-passing,
如果專案沒有通過所有測試,就會build-failing
一些觀點
實施單元測試的時候, 如果沒有一份經過實踐證明的詳細規範, 很難掌握測試的 “度”, 範圍太小施展不開, 太大又侵犯 “別人的” 地盤. 上帝的歸上帝, 凱撒的歸凱撒, 給單元測試念念緊箍咒不見得是件壞事, 反而更有利於發揮單元測試的威力, 為程式碼重構和提高程式碼質量提供動力.
這份文件來自 Geotechnical, 是一份非常難得的經驗準則. 你完全可以以這份準則作為模板, 結合所在團隊的經驗, 整理出一份內部單元測試準則.
彩蛋
最後,介紹一個庫:faker
他是一個能偽造使用者資料的庫,包括使用者常包含的屬性:個人資訊、頭像、地址等等。
是一個開發初期,模擬使用者資料的絕佳好庫。
支援Node.js和瀏覽器端。
整理
Nodejs的單元測試工具
- 測試框架 mocha
- 斷言庫:should.js、expect.js、chai
- 覆蓋率:istanbul、jscover、blanket
- Mock庫:muk
- 測試私有方法:rewire
- Web測試:supertest
- 持續整合:Travis-cli
相關文章
- 單元測試:單元測試中的mockMock
- 首次在WebAPI中寫單元測試WebAPI
- 單元測試在Unity中的應用Unity
- 在C#中進行單元測試C#
- Mock 在 Python 單元測試中的使用MockPython
- java中的單元測試Java
- c#中單元測試C#
- 在單元測試中使用EasyMockMock
- 測試 之Java單元測試、Android單元測試JavaAndroid
- 在.NET開發中的單元測試工具之——NUnit
- .NET 專案中的單元測試
- 在Model層如何做單元測試?
- [iOS單元測試系列]單元測試編碼規範iOS
- Flutter 單元測試Flutter
- Go單元測試Go
- 單元測試工具
- iOS 單元測試iOS
- 前端單元測試前端
- golang 單元測試Golang
- PHP 單元測試PHP
- phpunit單元測試PHP
- JUnit單元測試
- unittest單元測試
- Junit 單元測試.
- 單元測試真
- 有心無力,適合小專案的 Nodejs 單元測試NodeJS
- Java中的單元測試與整合測試最佳實踐Java
- 在開發過程中怎樣利用單元和功能測試
- 前端測試:Part II (單元測試)前端
- JavaScript單元測試框架JavaScript框架
- React元件單元測試React元件
- 聊聊前端單元測試前端
- Google 單元測試框架Go框架
- 單元測試 -- mocha + chaiAI
- 單元測試與MockitoMockito
- 單元測試基礎
- Vue單元測試探索Vue
- 單元測試與 PowerMockMock