nodejs--Nodejs單元測試小結

前言

最近在寫一課程的Project,用Node寫了一個實時聊天小應用,其中就用到了單元測試。在寫Node單元測試的時候,一方面感覺到了單元測試的重要性,另外一方面感覺到了Node單元測試的不夠成熟,還沒有有成熟的理論體系,因此想寫篇博客探討一下Node裏面單元測試的方法。示例代碼部署在Github上面,地址是:https://github.com/blogdemos/node-test-demo,歡迎fork~javascript

單元測試簡介

根據維基百科的定義:php

在計算機編程中,單元測試(又稱爲模塊測試, Unit Testing)是針對程序模塊(軟件設計的最小單位)來進行正確性檢驗的測試工做。程序單元是應用的最小可測試部件。在過程化編程中,一個單元就是單個程序、函數、過程等;對於面向對象編程,最小單元就是方法,包括基類(超類)、抽象類、或者派生類(子類)中的方法。前端

JavaScript是面向對象編程的,不少時候咱們都須要將一個功能掉抽象成一個組件,方便團隊其餘開發者調用,那麼咱們就理應保證咱們給出的組件是正確可用的。在很長的一段時間裏,前端都忽略了單元測試,或者說對於前端這種GUI編程來講,單元測試確實比較麻煩。隨着Node的異軍突起,針對JavaScript的單元測試框架如雨後春筍,前端也逐漸玩起了單元測試。java

單元測試的重要性是不言而喻的,常常存在的一個誤區是:node

  • 單元測試不是測試人員的事情麼?
  • 本身爲何要測試本身的代碼?
  • 單元測試的成本這麼高對於產品開發有意義麼?
  • 我這麼牛我不須要單元測試!

上面的幾點也是我以前懷疑過的,可是以爲如今每一點都是無可置疑的。首先,認爲測試是測試人員的事情是不負責的,測試人員更多的應該是針對總體功能,而每一位工程師應該保證本身代碼的準確性;其次本身測試本身的代碼更多的時候是爲了提升效率。若是我寫好了一個接口,沒有通過測試就直接交給了別人,那麼出錯以後就須要接着調試,其中所花費的溝通成本會大大大於編碼成本,這也順帶解決了第三個疑問;最後,在Github上面全部star過萬的repo都應該有本身的測試,我相信這些repo的做者比大部分人都牛,他們都須要不斷測試,咱們沒有理由不去測試。git

單元測試的分類

單元測試根據主流的分類能夠分紅兩類,分別是BDDTDDgithub

TDD

TDD的英文全稱是Test-Driven Development,即測試驅動開發。測試驅動開發的流程是
- 開發人員寫了一些測試代碼
- 開發人員跑了這些測試用例,而後毫無疑問的這些測試用例失敗了由於測試中提到的類和方法並無實現
- 開發人員開始實現測試用例裏面提到的方法
- 若是開發者寫好了某個功能點,他會欣喜地發現以前的相對應的測試用例經過了
- 開發者人員能夠重構代碼,並添加註釋,完成後期工做
這個流程以下圖:
web

BDD

BDD的英文全稱是Behavior-Driven Development,即行爲驅動開發。
BDD與TDD的主要區別是在寫測試案例的時候的措辭,BDD的測試案例更像是一份說明書,在詳細描述軟件的每個功能。我的比較喜歡BDD,後續的Demo也是BDD形式的。express

關於BDD和TDD的差異能夠看看這篇文章: The Difference Between TDD and BDDnpm

mocha框架簡介

說到JavaScript的測試框架,就不得不提起大名鼎鼎的TJ Holowaychuk寫的Mocha了。

簡介

Mocha是一個基於node.js和瀏覽器的集合各類特性的Javascript測試框架,而且可讓異步測試也變的簡單和有趣。Mocha的測試是連續的,在正確的測試條件中遇到未捕獲的異常時,會給出靈活且準確的報告。

安裝使用

npm install -g mocha 
$ npm install -g mocha
$ mkdir test
$ $EDITOR test/test.js var assert = require("assert"); describe('Array', function() { describe('#indexOf()', function() { it('should return -1 when the value is not present', function() { assert.equal(-1, [1,2,3].indexOf(5)); assert.equal(-1, [1,2,3].indexOf(0)); }); }); }); $ mocha . ✔ 1 test complete (1ms) 

輔助工具

爲了順利進行單元測試,一般都是組合幾種工具來使用的,這裏介紹經常使用的幾種。

should.js

should 是一個表述性、可讀性很強的測試無關的「斷言」庫。它是BDD風格的,用一個單例的不可枚舉的屬性訪問器擴展了Object的prototype,容許你表述對象應該展現的行爲。
node自己有本身的斷言模塊,可是should所具備的表述性和可讀性讓開發者沒有理由拒絕這麼棒的工具。
經常使用的斷言庫還有Chaiexpectjs,這裏再也不多說

supertest

在用Node作Web開發的時候,模擬HTTP請求時必不可少的,若是都須要用瀏覽器來實現請求,那就太Low了!
supertest是一個很是棒的適用於node的模擬HTTP請求的庫,有點拗口,可是看看dmeo就會米桑她優雅的鏈式寫法

var request = require('supertest') , express = require('express'); var app = express(); app.get('/user', function(req, res){ res.send(200, { name: 'tobi' }); }); request(app) .get('/user') .expect('Content-Type', /json/) .expect('Content-Length', '20') .expect(200) .end(function(err, res){ if (err) throw err; }); 

代碼示例

爲了可以展現測試的核心點,又能具備實戰性,咱們寫一個很是簡單的demo,這個demo有兩個主要功能-註冊登陸發佈簡單的話題
示例代碼託管在Github上面,地址是:https://github.com/blogdemos/node-test-demo.git

簡介

示例代碼採用nodejs的express框架寫了一個很是簡單的只有後臺的項目。爲了demo應有的簡介特性,省去了不少應當有的邏輯,力求展現測試過程當中應當注意的點。

目錄介紹

.
├── controllers                    // 控制層 | ├── site.js // 註冊登陸控制 | └── topic.js // 話題控制 ├── models // 數據模型 | ├── index.js // 出口文件 | ├── topic.js // 話題模型 | └── user.js // 用戶模型 ├── proxy // 數據控制層 | ├── topic.js // 話題數據控制 | └── user.js // 用戶數據控制 ├── tests // 單元測試 | ├── support/support.js // 模擬數據 | ├── user.test.js // 註冊登陸控制測試 | └── topic.test.js // 話題控制測試 ├── app.js // 項目主文件 ├── consig.js // 項目配置文件 ├── package.json // 包文件 └── router.js // 路由配置 

要點

1. 異步操做的測試

異步無阻塞I/O是Node的靈魂所在,由於用node開發的應用程序到處體現着異步的用法。在編寫測試案例的時候,咱們的測試代碼怎麼才能知道測試結果出來了呢?

強大的Mocha天然會考慮到這一點。只須要在你的測試結束時調用回調函數便可。經過給it()添加回調函數(一般命名爲done)能夠告知Mocha須要等待異步測試結束。這裏直接調用官網的例子:

describe('User', function() { describe('#save()', function() { it('should save without error', function(done) { var user = new User('Luna'); user.save(done); }); }); }); 
2. 具有正反測試用例

測試的一個重要環節就是要提升測試覆蓋率。話句話說,你在寫代碼的時候考慮到的異常狀況,在寫測試案例的時候也應該考慮在內,也就是所謂的正反測試案例。
舉個例子

describe('sign up', function() { it('should not sign up an user when loginname is empty', function(done) { request.post('/signup') .send({ loginname: '', password: password }) .expect(200, function(err, res) { should.not.exist(err); res.text.should.containEql('用戶名或密碼不能爲空'); done(); }); }); it('should not sign up an user when it is exist', function(done) { request.post('/signup') .send({ loginname: loginname, password: password }) .expect(200, function(err, res) { should.not.exist(err); res.text.should.containEql('用戶已經存在'); done(); }); }); }); 

在寫註冊登陸的測試案例的時候,咱們除了但願看到用戶名和密碼都正確填寫的狀況下獲得測試經過的結果,還但願看到咱們故意不輸入用戶名的時候也能獲得正確的"錯誤提示"。

3. 須要cookie和session的測試案例

在web開發中,Cookie有着很是重要的做用。由於HTTP是無狀態的,因此須要用cookie來輔助實現用戶認證。咱們先來簡單介紹一下cookie的工做機制。

若是所示,若是經過cookie和session協同識別一個用戶須要兩次請求,第一次請求的時候,服務器並不認識你,可是他給你標記了一個他獨有的id,等到第二次請求的時候,瀏覽器自動給你帶上了以前的標籤,這樣服務器就知道你以前請求過了。

那麼問題來了,若是咱們寫測試案例的時候,須要兩次請求來實現的話,會很是麻煩,測試案例也會很冗長。怎麼才能一次請求就能使用cookie和session呢?

這時候express的中間件的好處就體現了。
首先,咱們在用supertest進行HTTP請求的時候,能夠經過下面的形式設置cookie:

set('Cookie', cookieValue) 

而後,咱們寫一個很是簡單的中間件:

app.use(function(req, res, next) { if (config.debug && req.cookies['mock_user']) { var mockUser = JSON.parse(req.cookies['mock_user']); req.session.user = new UserModel(mockUser); return next(); } next(); }); 

原理就是先判斷當前是否爲開發環境,經過config來設置,一般在開發階段這個值設置爲true。其次判斷是否具備鍵爲mock_user的cookie鍵值對,若是存在,設置session裏面的user值,這樣,只要一次請求咱們就能實現用戶標識。
最後要解決的問題就是怎麼設置字段鍵爲mock_user的cookie了,具體的用法可參照test目錄裏面的support/support.js,這裏很少說。

4. 測試覆蓋率

爲了檢驗本身的測試用例是否全面,咱們須要知道本身的測試覆蓋率是多少。這裏介紹一個與mocha很是有好的istanbul。因爲本人是在windows下面寫的測試代碼,就不寫Makefile了,比較蛋疼。之因此強調Windows是由於在Windows運行istanbul的時候會會出現問題,具體見http://stackoverflow.com/questions/27084392/code-coverage-for-mocha-in...
所以在Windows運行的時候須要像下面這樣運行:

./node_modules/.bin/istanbul cover ./node_modules/mocha/bin/_mocha* 

結語

最後也不說什麼了,附上本地運行示例代碼的測試結果和測試覆蓋率結果:

參考資料

相關文章
相關標籤/搜索