建立一個(gè) lesson6 項(xiàng)目,在其中編寫(xiě)代碼。
main.js: 其中有個(gè) fibonacci 函數(shù)。fibonacci 的介紹見(jiàn):http://en.wikipedia.org/wiki/Fibonacci_number 。
此函數(shù)的定義為 int fibonacci(int n)
fibonacci(n) === fibonacci(n-1) + fibonacci(n-2)
,如 fibonacci(10) === 55
;test/main.test.js: 對(duì) main 函數(shù)進(jìn)行測(cè)試,并使行覆蓋率和分支覆蓋率都達(dá)到 100%。
首先,作為一個(gè) Node.js 項(xiàng)目,先執(zhí)行 npm init
創(chuàng)建 package.json。
其次,建立我們的 main.js 文件,編寫(xiě) fibonacci
函數(shù)。
var fibonacci = function (n) {
if (n === 0) {
return 0;
}
if (n === 1) {
return 1;
}
return fibonacci(n-1) + fibonacci(n-2);
};
if (require.main === module) {
// 如果是直接執(zhí)行 main.js,則進(jìn)入此處
// 如果 main.js 被其他文件 require,則此處不會(huì)執(zhí)行。
var n = Number(process.argv[2]);
console.log('fibonacci(' + n + ') is', fibonacci(n));
}
OK,這只是個(gè)簡(jiǎn)單的實(shí)現(xiàn)。
我們可以執(zhí)行試試
$ node main.js 10
嗯,結(jié)果是 55,符合預(yù)期。
接下來(lái)我們開(kāi)始測(cè)試驅(qū)動(dòng)開(kāi)發(fā),現(xiàn)在簡(jiǎn)單的實(shí)現(xiàn)已經(jīng)完成,那我們就對(duì)它進(jìn)行一下簡(jiǎn)單測(cè)試吧。
我們先得把 main.js 里面的 fibonacci 暴露出來(lái),這個(gè)簡(jiǎn)單。加一句
exports.fibonacci = fibonacci;
(要是看不懂這句就去補(bǔ)補(bǔ) Node.js 的基礎(chǔ)知識(shí)吧)
就好了。
然后我們?cè)?test/main.test.js
中引用我們的 main.js,并開(kāi)始一個(gè)簡(jiǎn)單的測(cè)試。
// file: test/main.test.js
var main = require('../main');
var should = require('should');
describe('test/main.test.js', function () {
it('should equal 55 when n === 10', function () {
main.fibonacci(10).should.equal(55);
});
});
把測(cè)試先跑通,我們?cè)僦v這段測(cè)試代碼的含義。
裝個(gè)全局的 mocha: $ npm install mocha -g
。
-g
與 非-g
的區(qū)別,就是安裝位置的區(qū)別,g 是 global 的意思。如果不加的話,則安裝 mocha 在你的項(xiàng)目目錄下面;如果加了,則這個(gè) mocha 是安裝在全局的,如果 mocha 有可執(zhí)行命令的話,那么這個(gè)命令也會(huì)自動(dòng)加入到你系統(tǒng) $PATH 中的某個(gè)地方(在我的系統(tǒng)中,是這里 /Users/alsotang/.nvm/v0.10.29/bin
)
在 lesson6 目錄下,直接執(zhí)行
$ mocha
輸出應(yīng)如下
那么,代碼中的 describe 和 it 是什么意思呢?其實(shí)就是 BDD 中的那些意思,把它們當(dāng)做語(yǔ)法來(lái)記就好了。
大家來(lái)看看 nodeclub 中,關(guān)于 topicController 的測(cè)試文件:
https://github.com/cnodejs/nodeclub/blob/master/test/controllers/topic.test.js
這文件的內(nèi)容沒(méi)有超出之前課程的范圍吧。
describe
中的字符串,用來(lái)描述你要測(cè)的主體是什么;it
當(dāng)中,描述具體的 case 內(nèi)容。
而引入的那個(gè) should 模塊,是個(gè)斷言庫(kù)。玩過(guò) ruby 的同學(xué)應(yīng)該知道 rspec
,rspec 它把測(cè)試框架和斷言庫(kù)的事情一起做了,而在 Node.js 中,這兩樣?xùn)|西的作用分別是 mocha 和 should 在協(xié)作完成。
should 在 js 的 Object “基類(lèi)”上注入了一個(gè) #should
屬性,這個(gè)屬性中,又有著許許多多的屬性可以被訪問(wèn)。
比如測(cè)試一個(gè)數(shù)是不是大于3,則是 (5).should.above(3)
;測(cè)試一個(gè)字符串是否有著特定前綴:'foobar'.should.startWith('foo');
。should.js API 在:https://github.com/tj/should.js
should.js 如果現(xiàn)在還是 version 3 的話,我倒是推薦大家去看看它的 API 和 源碼;現(xiàn)在 should 是 version 4 了,API 丑得很,但為了不掉隊(duì),我還是一直用著它。我覺(jué)得 expect 麻煩,所以不用 expect,對(duì)了,expect 也是一個(gè)斷言庫(kù):https://github.com/LearnBoost/expect.js/ 。
回到正題,還記得我們 fibonacci 函數(shù)的幾個(gè)要求嗎?
* 當(dāng) n === 0 時(shí),返回 0;n === 1時(shí),返回 1;
* n > 1 時(shí),返回 `fibonacci(n) === fibonacci(n-1) + fibonacci(n-2)`,如 `fibonacci(10) === 55`;
* n 不可大于10,否則拋錯(cuò),因?yàn)?Node.js 的計(jì)算性能沒(méi)那么強(qiáng)。
* n 也不可小于 0,否則拋錯(cuò),因?yàn)闆](méi)意義。
* n 不為數(shù)字時(shí),拋錯(cuò)。
我們用測(cè)試用例來(lái)描述一下這幾個(gè)要求,更新后的 main.test.js 如下:
var main = require('../main');
var should = require('should');
describe('test/main.test.js', function () {
it('should equal 0 when n === 0', function () {
main.fibonacci(0).should.equal(0);
});
it('should equal 1 when n === 1', function () {
main.fibonacci(1).should.equal(1);
});
it('should equal 55 when n === 10', function () {
main.fibonacci(10).should.equal(55);
});
it('should throw when n > 10', function () {
(function () {
main.fibonacci(11);
}).should.throw('n should <= 10');
});
it('should throw when n < 0', function () {
(function () {
main.fibonacci(-1);
}).should.throw('n should >= 0');
});
it('should throw when n isnt Number', function () {
(function () {
main.fibonacci('呵呵');
}).should.throw('n should be a Number');
});
});
還是比較清晰的吧?
我們這時(shí)候跑一下 $ mocha
,會(huì)發(fā)現(xiàn)后三個(gè) case 都沒(méi)過(guò)。
于是我們更新 fibonacci 的實(shí)現(xiàn):
var fibonacci = function (n) {
if (typeof n !== 'number') {
throw new Error('n should be a Number');
}
if (n < 0) {
throw new Error('n should >= 0')
}
if (n > 10) {
throw new Error('n should <= 10');
}
if (n === 0) {
return 0;
}
if (n === 1) {
return 1;
}
return fibonacci(n-1) + fibonacci(n-2);
};
再跑一次 $ mocha
,就過(guò)了。這就是傳說(shuō)中的測(cè)試驅(qū)動(dòng)開(kāi)發(fā):先把要達(dá)到的目的都描述清楚,然后讓現(xiàn)有的程序跑不過(guò) case,再修補(bǔ)程序,讓 case 通過(guò)。
安裝一個(gè) istanbul : $ npm i istanbul -g
執(zhí)行 $ istanbul cover _mocha
這會(huì)比直接使用 mocha 多一行覆蓋率的輸出,
可以看到,我們其中的分支覆蓋率是 91.67%,行覆蓋率是 87.5%。
打開(kāi) open coverage/lcov-report/index.html
看看
其實(shí)這覆蓋率是 100% 的,24 25 兩行沒(méi)法測(cè)。
mocha 和 istanbul 的結(jié)合是相當(dāng)無(wú)縫的,只要 mocha 跑得動(dòng),那么 istanbul 就接得進(jìn)來(lái)。
到此這門(mén)課其實(shí)就完了,剩下要說(shuō)的內(nèi)容,都是些比較細(xì)節(jié)的。比較懶的同學(xué)可以踩坑了之后再回來(lái)看。
上面的課程,不完美的地方就在于 mocha 和 istanbul 版本依賴的問(wèn)題,但為了不引入不必要的復(fù)雜性,所以上面就沒(méi)提到這點(diǎn)了。
假設(shè)你有一個(gè)項(xiàng)目A,用到了 mocha 的 version 3,其他人有個(gè)項(xiàng)目B,用到了 mocha 的 version 10,那么如果你 npm i mocha -g
裝的是 version 3 的話,你用 $ mocha
是不兼容B項(xiàng)目的。因?yàn)?mocha 版本改變之后,很可能語(yǔ)法也變了,對(duì)吧。
這時(shí),跑測(cè)試用例的正確方法,應(yīng)該是
$ npm i mocha --save-dev
,裝個(gè) mocha 到項(xiàng)目目錄中去$ ./node_modules/.bin/mocha
,用剛才安裝的這個(gè)特定版本的 mocha,來(lái)跑項(xiàng)目的測(cè)試代碼。./node_modules/.bin
這個(gè)目錄下放著我們所有依賴自帶的那些可執(zhí)行文件。
每次輸入這個(gè)很麻煩對(duì)吧?所以我們要引入 Makefile,讓 Makefile 幫我們記住復(fù)雜的配置。
test:
./node_modules/.bin/mocha
cov test-cov:
./node_modules/.bin/istanbul cover _mocha
.PHONY: test cov test-cov
這時(shí),我們只需要調(diào)用 make test
或者 make cov
,就可以跑我們相應(yīng)的測(cè)試了。
至于 Makefile 怎么寫(xiě)?以及 .PHONY 是什么意思,請(qǐng)看這里:http://blog.csdn.net/haoel/article/details/2886 ,左耳朵耗子陳皓2004年的文章。
更多建議: