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