Nodejs單元測試小結

jopen 9年前發布 | 71K 次閱讀 Node.js 開發 NodeJS

前言

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

單元測試簡介

根據維基百科的定義:

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

</div>

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

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

  • 單元測試不是測試人員的事情么?
  • 自己為什么要測試自己的代碼?
  • 單元測試的成本這么高對于產品開發有意義么?
  • 我這么牛我不需要單元測試!
  • </ul>

    上面的幾點也是我之前懷疑過的,但是覺得現在每一點都是不容置疑的。首先,認為測試是測試人員的事情是不負責的,測試人員更多的應該是針對整體功能,而每一位工程師應該保證自己代碼的準確性;其次自己測試自己的代碼更多的時候是為了提高效率。如果我寫好了一個接口,沒有經過測試就直接交給了別人,那么出錯之后就需要接著調試,其中所花費的溝通成本會大大大于編碼成本,這也順帶解決了第三個疑問;最后,在Github上面所有star過萬的repo都應該有自己的測試,我相信這些repo的作者比大部分人都牛,他們都需要不斷測試,我們沒有理由不去測試。

    單元測試的分類

    單元測試根據主流的分類可以分成兩類,分別是BDD 和TDD

    TDD

    TDD的英文全稱是Test-Driven Development,即測試驅動開發。測試驅動開發的流程是

    • 開發人員寫了一些測試代碼
    • 開發人員跑了這些測試用例,然后毫無疑問的這些測試用例失敗了因為測試中提到的類和方法并沒有實現
    • 開發人員開始實現測試用例里面提到的方法
    • 如果開發者寫好了某個功能點,他會欣喜地發現之前的相對應的測試用例通過了
    • 開發者人員可以重構代碼,并添加注釋,完成后期工作
      這個流程如下圖:
      Nodejs單元測試小結
    • </ul>

      BDD

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

      關于BDD和TDD的差別可以看看這篇文章: The Difference Between TDD and BDD

      mocha框架簡介

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

      簡介

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

      </div>

      安裝使用

      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 ,這里不再多說

      </div>

      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; });</pre>

      代碼示例

      為了能夠展示測試的核心點,又能具有實戰性,我們寫一個非常簡單的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的工作機制。

      Nodejs單元測試小結 </div>

      如果所示,如果通過cookie和session協同識別一個用戶需要兩次請求,第一次請求的時候,服務器并不認識你,但是他給你標記了一個他獨有的id,等到第二次請求的時候,瀏覽器自動給你帶上了之前的標簽,這樣服務器就知道你之前請求過了。

      那么問題來了,如果我們寫測試案例的時候,需要兩次請求來實現的話,會非常麻煩,測試案例也會很冗長。怎么才能一次請求就能使用cookie和session呢?

      這時候express的中間件的好處就體現了。

      首先,我們在用supertest 進行HTTP請求的時候,可以通過下面的形式設置cookie:

      </div>

      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 ,這里不多說。

      </div>

      4. 測試覆蓋率

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

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

      結語

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

      Nodejs單元測試小結 </div>

      Nodejs單元測試小結

      參考資料

       本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
       轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
       本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!