鍍金池/ 教程/ HTML/ 測(cè)試用例:supertest
瀏覽器端測(cè)試:mocha,chai,phantomjs
搭建 Node.js 開發(fā)環(huán)境
測(cè)試用例:mocha,should,istanbul
線上部署:heroku
Mongodb 與 Mongoose 的使用
使用 superagent 與 cheerio 完成簡(jiǎn)單爬蟲
js 中的那些最佳實(shí)踐
使用 eventproxy 控制并發(fā)
使用 promise 替代回調(diào)函數(shù)
作用域與閉包:this,var,(function () {})
持續(xù)集成平臺(tái):travis
測(cè)試用例:supertest
benchmark 怎么寫
使用 async 控制并發(fā)
學(xué)習(xí)使用外部模塊
一個(gè)最簡(jiǎn)單的 express 應(yīng)用
正則表達(dá)式
cookie 和 session

測(cè)試用例:supertest

目標(biāo)

建立一個(gè) lesson8 項(xiàng)目,在其中編寫代碼。

app.js: 其中有個(gè) fibonacci 接口。fibonacci 的介紹見:http://en.wikipedia.org/wiki/Fibonacci_number 。

fibonacci 函數(shù)的定義為 int fibonacci(int n),調(diào)用函數(shù)的路徑是 '/fib?n=10',然后這個(gè)接口會(huì)返回 '55'。函數(shù)的行為定義如下:

  • 當(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ò),http status 500,因?yàn)?Node.js 的計(jì)算性能沒(méi)那么強(qiáng)。
  • n 也不可小于 0,否則拋錯(cuò),500,因?yàn)闆](méi)意義。
  • n 不為數(shù)字時(shí),拋錯(cuò),500。

test/main.test.js: 對(duì) app 的接口進(jìn)行測(cè)試,覆蓋以上所有情況。

知識(shí)點(diǎn)

  1. 學(xué)習(xí) supertest 的使用 (https://github.com/tj/supertest )
  2. 復(fù)習(xí) mocha,should 的使用

課程內(nèi)容

這是連續(xù)第三節(jié)課講測(cè)試了..我自己都煩..看著煩的可以考慮跳下一課。

OK,基礎(chǔ)知識(shí)前面都講得很多了,這節(jié)課我不會(huì)事無(wú)巨細(xì)地寫過(guò)程了。

噢,對(duì)了,說(shuō)到 fibonacci,Node 中文圈的大神 @蘇千(https://github.com/fengmk2 ) 寫過(guò)一個(gè)頁(yè)面,對(duì)各種語(yǔ)言的 fibonacci 效率進(jìn)行了測(cè)試:http://fengmk2.cnpmjs.org/blog/2011/fibonacci/nodejs-python-php-ruby-lua.html 。其中,Node 的表現(xiàn)不知道比 Python 和 Ruby 高到哪里去了,與 CPU 談笑風(fēng)生。懷疑 js 的人啊,都 too simple,sometimes naive。

先來(lái)介紹一下 supertest。supertest 是 superagent 的孿生庫(kù)。他的作者叫 tj,這是個(gè)在 Node.js 的歷史上會(huì)永遠(yuǎn)被記住的名字,因?yàn)樗粋€(gè)人撐起了 npm 的半邊天。別誤會(huì)成他是 npm 的開發(fā)者,他的貢獻(xiàn)是在 Node.js 的方方面面都貢獻(xiàn)了非常高質(zhì)量和口碑的庫(kù),比如 mocha 是他的,superagent 是他的,express 是他的,should 也是他的,還有其他很多很多,比如 koa,都是他的。如果你更詳細(xì)點(diǎn)了解一些 Node 圈內(nèi)的八卦,一定也會(huì)像我一樣對(duì) tj 佩服得五體投地。他的 github 首頁(yè)是:https://github.com/tj 。

假使你作為一個(gè)有志之士,想要以他為榜樣,跟隨他前進(jìn)的步伐,那么我指條明路給你,不收費(fèi)的:http://tour.golang.org/

為什么說(shuō) supertest 是 superagent 的孿生庫(kù)呢,因?yàn)樗麄兊?API 是一模一樣的。superagent 是用來(lái)抓取頁(yè)面用的,而 supertest,是專門用來(lái)配合 express (準(zhǔn)確來(lái)說(shuō)是所有兼容 connect 的 web 框架)進(jìn)行集成測(cè)試的。

將使你有一個(gè) app: var app = express();,想對(duì)它的 get 啊,post 接口啊之類的進(jìn)行測(cè)試,那么只要把它傳給 supertest:var request = require('supertest')(app)。之后調(diào)用 requset.get('/path') 時(shí),就可以對(duì) app 的 path 路徑進(jìn)行訪問(wèn)了。它的 API 參照 superagent 的來(lái)就好了:http://visionmedia.github.io/superagent/

我們來(lái)新建一個(gè)項(xiàng)目

$ npm init # ..一陣亂填

然后安裝我們的依賴(記得去弄清楚 npm i --savenpm i --save-dev 的區(qū)別):

  "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 傳來(lái)的東西默認(rèn)都是沒(méi)有類型的,都是 String,所以我們要手動(dòng)轉(zhuǎn)換類型
  var n = Number(req.query.n);
  try {
    // 為何使用 String 做類型轉(zhuǎn)換,是因?yàn)槿绻阒苯咏o個(gè)數(shù)字給 res.send 的話,
    // 它會(huì)當(dāng)成是你給了它一個(gè) http 狀態(tài)碼,所以我們明確給 String
    res.send(String(fibonacci(n)));
  } catch (e) {
    // 如果 fibonacci 拋錯(cuò)的話,錯(cuò)誤信息會(huì)記錄在 err 對(duì)象的 .message 屬性中。
    // 拓展閱讀:https://www.joyent.com/developers/node/design/errors
    res
      .status(500)
      .send(e.message);
  }
});

// 暴露 app 出去。module.exports 與 exports 的區(qū)別請(qǐng)看《深入淺出 Node.js》
module.exports = app;

app.listen(3000, function () {
  console.log('app is listening at port 3000');
});

好了,啟動(dòng)一下看看。

$ node app.js

然后訪問(wèn) http://localhost:3000/fib?n=10,看到 55 就說(shuō)明啟動(dòng)成功了。再訪問(wèn) http://localhost:3000/fib?n=111,會(huì)看到 n should <= 10

對(duì)了,大家去裝個(gè) nodemon https://github.com/remy/nodemon 。

$ npm i -g nodemon

這個(gè)庫(kù)是專門調(diào)試時(shí)候使用的,它會(huì)自動(dòng)檢測(cè) node.js 代碼的改動(dòng),然后幫你自動(dòng)重啟應(yīng)用。在調(diào)試時(shí)可以完全用 nodemon 命令代替 node 命令。

$ nodemon app.js 啟動(dòng)我們的應(yīng)用試試,然后隨便改兩行代碼,就可以看到 nodemon 幫我們重啟應(yīng)用了。

那么 app 寫完了,接著開始測(cè)試,測(cè)試代碼在 test/app.test.js。

var app = require('../app');
var supertest = require('supertest');
// 看下面這句,這是關(guān)鍵一句。得到的 request 對(duì)象可以直接按照
// superagent 的 API 進(jìn)行調(diào)用
var request = supertest(app);

var should = require('should');

describe('test/app.test.js', function () {
  // 我們的第一個(gè)測(cè)試用例,好好理解一下
  it('should return 55 when n is 10', function (done) {
    // 之所以這個(gè)測(cè)試的 function 要接受一個(gè) done 函數(shù),是因?yàn)槲覀兊臏y(cè)試內(nèi)容
    // 涉及了異步調(diào)用,而 mocha 是無(wú)法感知異步調(diào)用完成的。所以我們主動(dòng)接受它提供
    // 的 done 函數(shù),在測(cè)試完畢時(shí),自行調(diào)用一下,以示結(jié)束。
    // mocha 可以感到到我們的測(cè)試函數(shù)是否接受 done 參數(shù)。js 中,function
    // 對(duì)象是有長(zhǎng)度的,它的長(zhǎng)度由它的參數(shù)數(shù)量決定
    // (function (a, b, c, d) {}).length === 4
    // 所以 mocha 通過(guò)我們測(cè)試函數(shù)的長(zhǎng)度就可以確定我們是否是異步測(cè)試。

    request.get('/fib')
    // .query 方法用來(lái)傳 querystring,.send 方法用來(lái)傳 body。
    // 它們都可以傳 Object 對(duì)象進(jìn)去。
    // 在這里,我們等于訪問(wèn)的是 /fib?n=10
      .query({n: 10})
      .end(function (err, res) {
        // 由于 http 返回的是 String,所以我要傳入 '55'。
        res.text.should.equal('55');

        // done(err) 這種用法寫起來(lái)很雞肋,是因?yàn)橥祽胁幌霚y(cè) err 的值
        // 如果勤快點(diǎn),這里應(yīng)該寫成
        /*
        should.not.exist(err);
        res.text.should.equal('55');
        */
        done(err);
      });
  });

  // 下面我們對(duì)于各種邊界條件都進(jìn)行測(cè)試,由于它們的代碼雷同,
  // 所以我抽象出來(lái)了一個(gè) testFib 方法。
  var testFib = function (n, expect, done) {
    request.get('/fib')
      .query({n: n})
      .end(function (err, res) {
        res.text.should.equal(expect);
        done(err);
      });
  };
  it('should return 0 when n === 0', function (done) {
    testFib(0, '0', done);
  });

  it('should equal 1 when n === 1', function (done) {
    testFib(1, '1', done);
  });

  it('should equal 55 when n === 10', function (done) {
    testFib(10, '55', done);
  });

  it('should throw when n > 10', function (done) {
    testFib(11, 'n should <= 10', done);
  });

  it('should throw when n < 0', function (done) {
    testFib(-1, 'n should >= 0', done);
  });

  it('should throw when n isnt Number', function (done) {
    testFib('good', 'n should be a Number', done);
  });

  // 單獨(dú)測(cè)試一下返回碼 500
  it('should status 500 when error', function (done) {
    request.get('/fib')
      .query({n: 100})
      .end(function (err, res) {
        res.status.should.equal(500);
        done(err);
      });
  });
});

完。

關(guān)于 cookie 持久化

有兩種思路

  1. 在 supertest 中,可以通過(guò) var agent = supertest.agent(app) 獲取一個(gè) agent 對(duì)象,這個(gè)對(duì)象的 API 跟直接在 superagent 上調(diào)用各種方法是一樣的。agent 對(duì)象在被多次調(diào)用 getpost 之后,可以一路把 cookie 都保存下來(lái)。

      var supertest = require('supertest');
      var app = express();
      var agent = supertest.agent(app);
    
      agent.post('login').end(...);
      // then ..
      agent.post('create_topic').end(...); // 此時(shí)的 agent 中有用戶登陸后的 cookie
    
  2. 在發(fā)起請(qǐng)求時(shí),調(diào)用 .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(...)

這里有個(gè)相關(guān)討論:https://github.com/tj/supertest/issues/46

拓展學(xué)習(xí)

Nodeclub 里面的測(cè)試使用的技術(shù)跟前面介紹的是一樣的,should mocha supertest 那套,應(yīng)該是很容易看懂的:

https://github.com/cnodejs/nodeclub/blob/master/test/controllers/topic.test.js