## 目標
建立一個 lesson8 項目,在其中編寫代碼。
app.js: 其中有個 fibonacci 接口。fibonacci 的介紹見:[http://en.wikipedia.org/wiki/Fibonacci_number](http://en.wikipedia.org/wiki/Fibonacci_number)?。
fibonacci 函數的定義為?`int fibonacci(int n)`,調用函數的路徑是 '/fib?n=10',然后這個接口會返回 '55'。函數的行為定義如下:
* 當 n === 0 時,返回 0;n === 1時,返回 1;
* n > 1 時,返回?`fibonacci(n) === fibonacci(n-1) + fibonacci(n-2)`,如?`fibonacci(10) === 55`;
* n 不可大于10,否則拋錯,http status 500,因為 Node.js 的計算性能沒那么強。
* n 也不可小于 0,否則拋錯,500,因為沒意義。
* n 不為數字時,拋錯,500。
test/main.test.js: 對 app 的接口進行測試,覆蓋以上所有情況。
## [](https://github.com/alsotang/node-lessons/tree/master/lesson8#知識點)知識點
1. 學習 supertest 的使用 ([https://github.com/tj/supertest](https://github.com/tj/supertest)?)
2. 復習 mocha,should 的使用
## [](https://github.com/alsotang/node-lessons/tree/master/lesson8#課程內容)課程內容
這是連續第三節課講測試了..我自己都煩..看著煩的可以考慮跳下一課。
OK,基礎知識前面都講得很多了,這節課我不會事無巨細地寫過程了。
噢,對了,說到 fibonacci,Node 中文圈的大神 @蘇千([https://github.com/fengmk2](https://github.com/fengmk2)?) 寫過一個頁面,對各種語言的 fibonacci 效率進行了測試:[http://fengmk2.cnpmjs.org/blog/2011/fibonacci/nodejs-python-php-ruby-lua.html](http://fengmk2.cnpmjs.org/blog/2011/fibonacci/nodejs-python-php-ruby-lua.html)?。其中,Node 的表現不知道比 Python 和 Ruby 高到哪里去了,與 CPU 談笑風生。懷疑 js 的人啊,都 too simple,sometimes naive。
先來介紹一下 supertest。supertest 是 superagent 的孿生庫。他的作者叫 tj,這是個在 Node.js 的歷史上會永遠被記住的名字,因為他一個人撐起了 npm 的半邊天。別誤會成他是 npm 的開發者,他的貢獻是在 Node.js 的方方面面都貢獻了非常高質量和口碑的庫,比如 mocha 是他的,superagent 是他的,express 是他的,should 也是他的,還有其他很多很多,比如 koa,都是他的。如果你更詳細點了解一些 Node 圈內的八卦,一定也會像我一樣對 tj 佩服得五體投地。他的 github 首頁是:[https://github.com/tj](https://github.com/tj)?。
假使你作為一個有志之士,想要以他為榜樣,跟隨他前進的步伐,那么我指條明路給你,不收費的:[http://tour.golang.org/](http://tour.golang.org/)
為什么說 supertest 是 superagent 的孿生庫呢,因為他們的 API 是一模一樣的。superagent 是用來抓取頁面用的,而 supertest,是專門用來配合 express (準確來說是所有兼容 connect 的 web 框架)進行集成測試的。
將使你有一個 app:?`var app = express();`,想對它的 get 啊,post 接口啊之類的進行測試,那么只要把它傳給 supertest:`var request = require('supertest')(app)`。之后調用?`requset.get('/path')`?時,就可以對 app 的 path 路徑進行訪問了。它的 API 參照 superagent 的來就好了:[http://visionmedia.github.io/superagent/](http://visionmedia.github.io/superagent/)?。
我們來新建一個項目
~~~
$ npm init # ..一陣亂填
~~~
然后安裝我們的依賴(記得去弄清楚?`npm i --save`?與?`npm i --save-dev`?的區別):
~~~
"devDependencies": {
"mocha": "^1.21.4",
"should": "^4.0.4",
"supertest": "^0.14.0"
},
"dependencies": {
"express": "^4.9.6"
}
~~~
接著,編寫 app.js
~~~
var express = require('express');
// 與之前一樣
var fibonacci = function (n) {
// typeof NaN === 'number' 是成立的,所以要判斷 NaN
if (typeof n !== 'number' || isNaN(n)) {
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);
};
// END 與之前一樣
var app = express();
app.get('/fib', function (req, res) {
// http 傳來的東西默認都是沒有類型的,都是 String,所以我們要手動轉換類型
var n = Number(req.query.n);
try {
// 為何使用 String 做類型轉換,是因為如果你直接給個數字給 res.send 的話,
// 它會當成是你給了它一個 http 狀態碼,所以我們明確給 String
res.send(String(fibonacci(n)));
} catch (e) {
// 如果 fibonacci 拋錯的話,錯誤信息會記錄在 err 對象的 .message 屬性中。
// 拓展閱讀:https://www.joyent.com/developers/node/design/errors
res
.status(500)
.send(e.message);
}
});
// 暴露 app 出去。module.exports 與 exports 的區別請看《深入淺出 Node.js》
module.exports = app;
app.listen(3000, function () {
console.log('app is listening at port 3000');
});
~~~
好了,啟動一下看看。
~~~
$ node app.js
~~~
然后訪問?`http://localhost:3000/fib?n=10`,看到 55 就說明啟動成功了。再訪問?`http://localhost:3000/fib?n=111`,會看到?`n should <= 10`。
對了,大家去裝個?`nodemon`?[https://github.com/remy/nodemon](https://github.com/remy/nodemon)?。
`$ npm i -g nodemon`
這個庫是專門調試時候使用的,它會自動檢測 node.js 代碼的改動,然后幫你自動重啟應用。在調試時可以完全用 nodemon 命令代替 node 命令。
`$ nodemon app.js`?啟動我們的應用試試,然后隨便改兩行代碼,就可以看到 nodemon 幫我們重啟應用了。
那么 app 寫完了,接著開始測試,測試代碼在 test/app.test.js。
~~~
var app = require('../app');
var supertest = require('supertest');
// 看下面這句,這是關鍵一句。得到的 request 對象可以直接按照
// superagent 的 API 進行調用
var request = supertest(app);
var should = require('should');
describe('test/app.test.js', function () {
// 我們的第一個測試用例,好好理解一下
it('should return 55 when n is 10', function (done) {
// 之所以這個測試的 function 要接受一個 done 函數,是因為我們的測試內容
// 涉及了異步調用,而 mocha 是無法感知異步調用完成的。所以我們主動接受它提供
// 的 done 函數,在測試完畢時,自行調用一下,以示結束。
// mocha 可以感到到我們的測試函數是否接受 done 參數。js 中,function
// 對象是有長度的,它的長度由它的參數數量決定
// (function (a, b, c, d) {}).length === 4
// 所以 mocha 通過我們測試函數的長度就可以確定我們是否是異步測試。
request.get('/fib')
// .query 方法用來傳 querystring,.send 方法用來傳 body。
// 它們都可以傳 Object 對象進去。
// 在這里,我們等于訪問的是 /fib?n=10
.query({n: 10})
.end(function (err, res) {
// 由于 http 返回的是 String,所以我要傳入 '55'。
res.text.should.equal('55');
// done(err) 這種用法寫起來很雞肋,是因為偷懶不想測 err 的值
// 如果勤快點,這里應該寫成
/*
should.not.exist(err);
res.text.should.equal('55');
*/
done(err);
});
});
// 下面我們對于各種邊界條件都進行測試,由于它們的代碼雷同,
// 所以我抽象出來了一個 testFib 方法。
var testFib = function (n, statusCode, expect, done) {
request.get('/fib')
.query({n: n})
.expect(statusCode)
.end(function (err, res) {
res.text.should.equal(expect);
done(err);
});
};
it('should return 0 when n === 0', function (done) {
testFib(0, 200, '0', done);
});
it('should equal 1 when n === 1', function (done) {
testFib(1, 200, '1', done);
});
it('should equal 55 when n === 10', function (done) {
testFib(10, 200, '55', done);
});
it('should throw when n > 10', function (done) {
testFib(11, 500, 'n should <= 10', done);
});
it('should throw when n < 0', function (done) {
testFib(-1, 500, 'n should >= 0', done);
});
it('should throw when n isnt Number', function (done) {
testFib('good', 500, 'n should be a Number', done);
});
// 單獨測試一下返回碼 500
it('should status 500 when error', function (done) {
request.get('/fib')
.query({n: 100})
.expect(500)
.end(function (err, res) {
done(err);
});
});
});
~~~
完。
## [](https://github.com/alsotang/node-lessons/tree/master/lesson8#關于-cookie-持久化)關于 cookie 持久化
有兩種思路
1. 在 supertest 中,可以通過?`var agent = supertest.agent(app)`?獲取一個 agent 對象,這個對象的 API 跟直接在 superagent 上調用各種方法是一樣的。agent 對象在被多次調用?`get`?和?`post`?之后,可以一路把 cookie 都保存下來。
~~~
var supertest = require('supertest');
var app = express();
var agent = supertest.agent(app);
agent.post('login').end(...);
// then ..
agent.post('create_topic').end(...); // 此時的 agent 中有用戶登陸后的 cookie
~~~
2. 在發起請求時,調用?`.set('Cookie', 'a cookie string')`?這樣的方式。
~~~
var supertest = require('supertest');
var userCookie;
supertest.post('login').end(function (err, res) {
userCookie = res.headers['Cookie']
});
// then ..
supertest.post('create_topic')
.set('Cookie', userCookie)
.end(...)
~~~
這里有個相關討論:[https://github.com/tj/supertest/issues/46](https://github.com/tj/supertest/issues/46)
## [](https://github.com/alsotang/node-lessons/tree/master/lesson8#拓展學習)拓展學習
Nodeclub 里面的測試使用的技術跟前面介紹的是一樣的,should mocha supertest 那套,應該是很容易看懂的:
[https://github.com/cnodejs/nodeclub/blob/master/test/controllers/topic.test.js](https://github.com/cnodejs/nodeclub/blob/master/test/controllers/topic.test.js)
- 關于
- Lesson 0: 《搭建 Node.js 開發環境》
- Lesson 1: 《一個最簡單的 express 應用》
- Lesson 2: 《學習使用外部模塊》
- Lesson 3: 《使用 superagent 與 cheerio 完成簡單爬蟲》
- Lesson 4: 《使用 eventproxy 控制并發》
- Lesson 5: 《使用 async 控制并發》
- Lesson 6: 《測試用例:mocha,should,istanbul》
- Lesson 7: 《瀏覽器端測試:mocha,chai,phantomjs》
- Lesson 8: 《測試用例:supertest》
- Lesson 9: 《正則表達式》
- Lesson 10: 《benchmark 怎么寫》
- Lesson 11: 《作用域與閉包:this,var,(function () {})》
- Lesson 12: 《線上部署:heroku》
- Lesson 13: 《持續集成平臺:travis》
- Lesson 14: 《js 中的那些最佳實踐》
- Lesson 15: 《Mongodb 與 Mongoose 的使用》
- Lesson 16: 《cookie 與 session》
- Lesson 17: 《使用 promise 替代回調函數》