摘要:原文鏈接在中貫徹單元測(cè)試在團(tuán)隊(duì)合作中,你寫好了一個(gè)函數(shù),供隊(duì)友使用,跑去跟你的隊(duì)友說,你傳個(gè)值進(jìn)去,他就會(huì)返回結(jié)果了。如果你也為社區(qū)貢獻(xiàn)過,想更多人使用的話,加上單元測(cè)試吧,讓你的值得別人信賴。
原文鏈接:BlueSun | 在Nodejs中貫徹單元測(cè)試
在團(tuán)隊(duì)合作中,你寫好了一個(gè)函數(shù),供隊(duì)友使用,跑去跟你的隊(duì)友說,你傳個(gè)A值進(jìn)去,他就會(huì)返回B結(jié)果了。過了一會(huì),你隊(duì)友跑過來說,我傳個(gè)A值卻返回C結(jié)果,怎么回事?你丫的有沒有測(cè)試過?。?/p>
大家一起寫個(gè)項(xiàng)目,難免會(huì)有我要寫的函數(shù)里面依賴別人的函數(shù),但是這個(gè)函數(shù)到底值不值得信賴?單元測(cè)試是衡量代碼質(zhì)量的一重要標(biāo)準(zhǔn),縱觀Github的受歡迎項(xiàng)目,都是有test文件夾,并且buliding-pass的。如果你也為社區(qū)貢獻(xiàn)過module,想更多人使用的話,加上單元測(cè)試吧,讓你的module值得別人信賴。
要在Nodejs中寫單元測(cè)試的話,你需要知道用什么測(cè)試框架,怎么測(cè)試異步函數(shù),怎么測(cè)試私有方法,怎么模擬測(cè)試環(huán)境,怎么測(cè)試依賴HTTP協(xié)議的web應(yīng)用,需要了解TDD和BDD,還有需要提供測(cè)試的覆蓋率。
目錄本文的示例代碼會(huì)備份到 Github : unittest-demo
測(cè)試框架
斷言庫(kù)
需求變更
異步測(cè)試
異常測(cè)試
測(cè)試私有方法
測(cè)試Web應(yīng)用
覆蓋率
使用Makefile把測(cè)試串起來
持續(xù)集成,Travis-cli
一些觀點(diǎn)
彩蛋
整理
測(cè)試框架Nodejs的測(cè)試框架還用說?大家都在用,Mocha。
Mocha 是一個(gè)功能豐富的Javascript測(cè)試框架,它能運(yùn)行在Node.js和瀏覽器中,支持BDD、TDD、QUnit、Exports式的測(cè)試,本文主要示例是使用更接近與思考方式的BDD,如果了解更多可以訪問Mocha的官網(wǎng)
測(cè)試接口Mocha的BDD接口有:
describe()
it()
before()
after()
beforeEach()
afterEach()
安裝npm install mocha -g
編寫一個(gè)穩(wěn)定可靠的模塊模塊具備limit方法,輸入一個(gè)數(shù)值,小于0的時(shí)候返回0,其余正常返回
exports.limit = function (num) { if (num < 0) { return 0; } return num; };目錄分配
lib,存放模塊代碼的地方
test,存放單元測(cè)試代碼的地方
index.js,向外導(dǎo)出模塊的地方
package.json,包描述文件
測(cè)試var lib = require("index"); describe("module", function () { describe("limit", function () { it("limit should success", function () { lib.limit(10); }); }); });結(jié)果
在當(dāng)前目錄下執(zhí)行mocha:
$ mocha ? ? 1 test complete (2ms)斷言庫(kù)
上面的代碼只是運(yùn)行了代碼,并沒有對(duì)結(jié)果進(jìn)行檢查,這時(shí)候就要用到斷言庫(kù)了,Node.js中常用的斷言庫(kù)有:
should.js
expect.js
chai
加上斷言使用should庫(kù)為測(cè)試用例加上斷言
it("limit should success", function () { lib.limit(10).should.be.equal(10); });需求變更
需求變更啦:?limit這個(gè)方法還要求返回值大于100時(shí)返回100。
針對(duì)需求重構(gòu)代碼之后,正是測(cè)試用例的價(jià)值所在了,
它能確保你的改動(dòng)對(duì)原有成果沒有造成破壞。
但是,你要多做的一些工作的是,需要為新的需求編寫新的測(cè)試代碼。
異步測(cè)試 測(cè)試異步回調(diào)lib庫(kù)中新增async函數(shù):
exports.async = function (callback) { setTimeout(function () { callback(10); }, 10); };
測(cè)試異步代碼:
describe("async", function () { it("async", function (done) { lib.async(function (result) { done(); }); }); });測(cè)試Promise
使用should提供的Promise斷言接口:
finally | eventually
fulfilled
fulfilledWith
rejected
rejectedWith
then
測(cè)試代碼
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"); }) }); }); });異步方法的超時(shí)支持
Mocha的超時(shí)設(shè)定默認(rèn)是2s,如果執(zhí)行的測(cè)試超過2s的話,就會(huì)報(bào)timeout錯(cuò)誤。
可以主動(dòng)修改超時(shí)時(shí)間,有兩種方法。
命令行式mocha -t 10000
API式describe("async", function () { this.timeout(10000); it("async", function (done) { lib.async(function (result) { done(); }); }); });
這樣的話async執(zhí)行時(shí)間不超過10s,就不會(huì)報(bào)錯(cuò)timeout錯(cuò)誤了。
異常測(cè)試異常應(yīng)該怎么測(cè)試,現(xiàn)在有getContent方法,他會(huì)讀取指定文件的內(nèi)容,但是不一定會(huì)成功,會(huì)拋出異常。
exports.getContent = function (filename, callback) { fs.readFile(filename, "utf-8", callback); };
這時(shí)候就應(yīng)該模擬(mock)錯(cuò)誤環(huán)境了
簡(jiǎn)單Mockdescribe("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庫(kù)
Mock小模塊:muk ,略微優(yōu)美的寫法:
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(); });測(cè)試私有方法
針對(duì)一些內(nèi)部的方法,沒有通過exports暴露出來,怎么測(cè)試它?
function _adding(num1, num2) { return num1 + num2; }通過rewire導(dǎo)出方法
模塊:rewire
it("limit should return success", function () { var lib = rewire("../lib/index.js"); var litmit = lib.__get__("limit"); litmit(10); });測(cè)試Web應(yīng)用
在開發(fā)Web項(xiàng)目的時(shí)候,要測(cè)試某一個(gè)API,如:/user,到底怎么編寫測(cè)試用例呢?
使用: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(); }) }); });覆蓋率
測(cè)試的時(shí)候,我們常常關(guān)心,是否所有代碼都測(cè)試到了。
這個(gè)指標(biāo)就叫做"代碼覆蓋率"(code coverage)。它有四個(gè)測(cè)量維度。
行覆蓋率(line coverage):是否每一行都執(zhí)行了?
函數(shù)覆蓋率(function coverage):是否每個(gè)函數(shù)都調(diào)用了?
分支覆蓋率(branch coverage):是否每個(gè)if代碼塊都執(zhí)行了?
語句覆蓋率(statement coverage):是否每個(gè)語句都執(zhí)行了?
Istanbul?是 JavaScript 程序的代碼覆蓋率工具。
安裝$ npm install -g istanbul
覆蓋率測(cè)試在編寫過以上的測(cè)試用例之后,執(zhí)行命令:
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 ) ==========================================================
這條命令同時(shí)還生成了一個(gè) coverage 子目錄,其中的 coverage.json 文件包含覆蓋率的原始數(shù)據(jù),coverage/lcov-report 是可以在瀏覽器打開的覆蓋率報(bào)告,其中有詳細(xì)信息,到底哪些代碼沒有覆蓋到。
上面命令中,istanbul cover 命令后面跟的是 _mocha 命令,前面的下劃線是不能省略的。
因?yàn)?,mocha 和 _mocha 是兩個(gè)不同的命令,前者會(huì)新建一個(gè)進(jìn)程執(zhí)行測(cè)試,而后者是在當(dāng)前進(jìn)程(即 istanbul 所在的進(jìn)程)執(zhí)行測(cè)試,只有這樣, istanbul 才會(huì)捕捉到覆蓋率數(shù)據(jù)。其他測(cè)試框架也是如此,必須在同一個(gè)進(jìn)程執(zhí)行測(cè)試。
如果要向 mocha 傳入?yún)?shù),可以寫成下面的樣子。
$ istanbul cover _mocha -- tests/test.sqrt.js -R spec
上面命令中,兩根連詞線后面的部分,都會(huì)被當(dāng)作參數(shù)傳入 Mocha 。如果不加那兩根連詞線,它們就會(huì)被當(dāng)作 istanbul 的參數(shù)(參考鏈接1,2)。
使用Makefile串起項(xiàng)目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
用項(xiàng)目自身的jscover和mocha,避免版本沖突和混亂
持續(xù)集成,Travis-cli
Travis-ci
綁定Github帳號(hào)
在Github倉(cāng)庫(kù)的Admin打開Services hook
打開Travis
每次push將會(huì)hook觸發(fā)執(zhí)行npm test命令
注意:Travis會(huì)將未描述的項(xiàng)目當(dāng)作Ruby項(xiàng)目。所以需要在根目錄下加入.travis.yml文件。內(nèi)容如下:
language: node_js node_js: - "0.12"
Travis-cli還會(huì)對(duì)項(xiàng)目頒發(fā)標(biāo)簽,
or?
如果項(xiàng)目通過所有測(cè)試,就會(huì)build-passing,
如果項(xiàng)目沒有通過所有測(cè)試,就會(huì)build-failing
一些觀點(diǎn)實(shí)施單元測(cè)試的時(shí)候, 如果沒有一份經(jīng)過實(shí)踐證明的詳細(xì)規(guī)范, 很難掌握測(cè)試的 "度", 范圍太小施展不開, 太大又侵犯 "別人的" 地盤. 上帝的歸上帝, 凱撒的歸凱撒, 給單元測(cè)試念念緊箍咒不見得是件壞事, 反而更有利于發(fā)揮單元測(cè)試的威力, 為代碼重構(gòu)和提高代碼質(zhì)量提供動(dòng)力.
這份文檔來自 Geotechnical, 是一份非常難得的經(jīng)驗(yàn)準(zhǔn)則. 你完全可以以這份準(zhǔn)則作為模板, 結(jié)合所在團(tuán)隊(duì)的經(jīng)驗(yàn), 整理出一份內(nèi)部單元測(cè)試準(zhǔn)則.
單元測(cè)試準(zhǔn)則
彩蛋最后,介紹一個(gè)庫(kù):faker
他是一個(gè)能偽造用戶數(shù)據(jù)的庫(kù),包括用戶常包含的屬性:個(gè)人信息、頭像、地址等等。
是一個(gè)開發(fā)初期,模擬用戶數(shù)據(jù)的絕佳好庫(kù)。
支持Node.js和瀏覽器端。
整理 Nodejs的單元測(cè)試工具測(cè)試框架 mocha
斷言庫(kù):should.js、expect.js、chai
覆蓋率:istanbul、jscover、blanket
Mock庫(kù):muk
測(cè)試私有方法:rewire
Web測(cè)試:supertest
持續(xù)集成:Travis-cli
參考https://github.com/JacksonTian/unittesting
]()[http://html5ify.com/unittesting/slides/index.html
http://www.ruanyifeng.com/blog/2015/06/istanbul.html
http://coolshell.cn/articles/8209.html
http://stackoverflow.com/questions/153234/how-deep-are-your-unit-tests
https://github.com/yangyubo/zh-unit-testing-guidelines
http://www.codedata.com.tw/java/unit-test-the-way-changes-my-programming
http://wiki.ubuntu.org.cn/%E8%B7%9F%E6%88%91%E4%B8%80%E8%B5%B7%E5%86%99Makefile:MakeFile%E4%BB%8B%E7%BB%8D
https://github.com/yangyubo/zh-unit-testing-guidelines
https://github.com/visionmedia/superagent/blob/master/Makefile
如果本文對(duì)您有用
請(qǐng)不要吝嗇你們的Follow與Start
這會(huì)大大支持我們繼續(xù)創(chuàng)作
「Github」
MZMonster :@MZMonster
JC_Huang :@JerryC8080
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/78938.html
摘要:下面我們就羅列閉包的幾個(gè)常見問題,從回答問題的角度來理解和定義你們心中的閉包。函數(shù)可以通過作用域鏈相互關(guān)聯(lián)起來,函數(shù)內(nèi)部的變量可以保存在其他函數(shù)作用域內(nèi),這種特性在計(jì)算機(jī)科學(xué)文獻(xiàn)中稱為閉包。 寫這篇文章之前,我對(duì)閉包的概念及原理模糊不清,一直以來都是以通俗的外層函數(shù)包裹內(nèi)層....來欺騙自己。并沒有說這種說法的對(duì)與錯(cuò),我只是不想擁有從眾心理或者也可以說如果我們說出更好更低層的東西,逼格...
摘要:?jiǎn)卧獪y(cè)試上一節(jié)有討論過,單元測(cè)試就是以代碼單元為單位進(jìn)行測(cè)試,代碼單元可以是一個(gè)函數(shù),一個(gè)模塊,或者一個(gè)類。單元測(cè)試是最容易理解也最容易實(shí)現(xiàn)的測(cè)試方式。在寫單元測(cè)試的時(shí)候,盡量將你的單元測(cè)試獨(dú)立出來,不要幾個(gè)單元互相引用。 showImg(https://segmentfault.com/img/remote/1460000008823416?w=997&h=350); 本文作者:G...
摘要:?jiǎn)卧獪y(cè)試上一節(jié)有討論過,單元測(cè)試就是以代碼單元為單位進(jìn)行測(cè)試,代碼單元可以是一個(gè)函數(shù),一個(gè)模塊,或者一個(gè)類。單元測(cè)試是最容易理解也最容易實(shí)現(xiàn)的測(cè)試方式。在寫單元測(cè)試的時(shí)候,盡量將你的單元測(cè)試獨(dú)立出來,不要幾個(gè)單元互相引用。 showImg(https://segmentfault.com/img/remote/1460000008823416?w=997&h=350); 本文作者:G...
摘要:原文鏈接消息系統(tǒng)設(shè)計(jì)與實(shí)現(xiàn)上篇由于文章篇幅較長(zhǎng),而作者精力有限,不希望這么早就精盡人亡,故分成上下篇來寫消息系統(tǒng)的設(shè)計(jì)與實(shí)現(xiàn)。更新于關(guān)聯(lián)文章消息系統(tǒng)設(shè)計(jì)與實(shí)現(xiàn)下篇如果本文對(duì)您有用請(qǐng)不要吝嗇你們的與這會(huì)大大支持我們繼續(xù)創(chuàng)作 原文鏈接:Bluesun | 消息系統(tǒng)設(shè)計(jì)與實(shí)現(xiàn)「上篇」 由于文章篇幅較長(zhǎng),而作者精力有限,不希望這么早就精盡人亡,故分成上下篇來寫消息系統(tǒng)的設(shè)計(jì)與實(shí)現(xiàn)。上篇主要講...
摘要:很快我發(fā)現(xiàn)有一個(gè)誤區(qū),許多人認(rèn)為單元測(cè)試必須是一個(gè)集中運(yùn)行所有單元的測(cè)試,并一目了然。許多人認(rèn)為單元測(cè)試,甚至整個(gè)測(cè)試都是在編碼結(jié)束后的一道工序,而修復(fù)也不過是在做垃圾掩埋一類的工作。 單元測(cè)試Unit Test 很早就知道單元測(cè)試這樣一個(gè)概念,但直到幾個(gè)月前,我真正開始接觸和使用它。究竟什么是單元測(cè)試?我想也許很多使用了很久的人也不一定能描述的十分清楚,所以寫了這篇文章來嘗試描述它...
閱讀 1457·2019-08-29 17:14
閱讀 1655·2019-08-29 12:12
閱讀 738·2019-08-29 11:33
閱讀 3273·2019-08-28 18:27
閱讀 1449·2019-08-26 10:19
閱讀 912·2019-08-23 18:18
閱讀 3534·2019-08-23 16:15
閱讀 2548·2019-08-23 14:14