Nodejs測試:從0到90(理論篇)
最近在負責一個基於nodejs的應用,在很多方面都經歷了一個從無到有的過程,測試也是如此。剛開始時,程式碼都寫不好,更別提測試了,那時測試為0。經歷過一段時間後,尤其是看到 npm 上優秀的庫的測試覆蓋率都在100%時,痛下決心開始學習 nodejs 的測試, 目前這個應用的測試覆蓋率在90%+。這就是我的從0到90。剩下的10,還有很長的路,且待下回分解。
寫在前面的
對於開發者,測試的重要性毋庸置疑, 所謂”無測試不上線“,”無測試不重構“等。但在實踐的過程中,測試總是有這樣的那樣的問題,隨便列舉下:
- 不寫測試。一般是覺得寫測試浪費時間,也有可能是不會寫;
- 亂寫測試。隨意的寫,沒有規範,沒有覆蓋率,沒有整合;
- 寫了跟沒寫一樣。這樣的測試通常是不可讀、不可維護、不可信任,不可重複或獨立執行(強依賴某些特定的環境或條件)、不執行(通常只是開發的時候執行一下,之後再也不會執行,或者執行太慢,沒有人願意執行)。
一個好的測試
一千個人眼中有至少二千種測試理念或方法,很難定義什麼是一種好的測試。在團隊一直負責測試的推進,我們的理念很簡單,包括以下幾個原則:
- 覆蓋75%+
一個好的測試,最重要的衡量標準就是測了多少(覆蓋率),75%是最低的標準。這個標準對java來說基本可行,但對nodejs不太適用,javascript是弱型別的、動態語言,沒有編譯階段,很多錯誤只能在執行時才會被發現,所以需要更高的覆蓋率,最好是100%,目前個人的標準是90%+。
- 可重複執行
每一個測試用例,無論在任何環境下,都應該可以反覆執行,併產生相同的結果。只有這樣,才能夠相任你的測試,進而發現真正的bug。這也是整合測試最低要求。
- 保持獨立
一個測試用例只測試程式碼的某一方面,如一個分支,且不強依賴某些特定的環境或條件。
- 可讀、可維護、可信任
- 快快快
無論是單個測試用例,還是整合測試,必須要保證測試執行足夠的快。
測什麼
測試測什麼主要依據具體的需求、業務、成本、語言等,但也有一定的共性,單元測試準則 給出了一些準則可供參考,這裡不再討論。
怎麼測
又是一個非常大的話題,本文僅從個人角度給出nodejs測試的工具及方法。
概述
框架
Nodejs 測試框架眾多,目前使用最廣的是Mocha。本文詳細說明下 Mocha, 並簡要介紹幾種其它框架。本文的示例,如無特別說明,都是基於Mocha.
Mocha
A simple, flexible, fun JavaScript test framework for node.js and the browser.
Mocha 是一個功能豐富的Javascript測試框架,它能執行在Node.js和瀏覽器中,支援BDD、TDD式的測試。
快速開始
- 安裝
npm install -g mocha
- 寫一個簡的測試用例
var assert = require(`chai`).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)
使用
斷言
Mocha 允許你使用任何你想用的斷言庫,包括:
- should.js BDD style shown throughout these docs
- expect.js expect() style assertions
- chai expect(), assert() and should style assertions
- better-assert c-style self-documenting assert()
- unexpected the extensible BDD assertion toolkit
鉤子 Hooks
Mocha 提供了 before()
, after()
, beforeEach()
, afterEach()
等 hooks,用於設定前置條件及清理測試,示例如下:
describe(`hooks`, function() {
before(function() {
// runs before all tests in this block
})
after(function(){
// runs after all tests in this block
})
beforeEach(function(){
// runs before each test in this block
})
afterEach(function(){
// runs after each test in this block
})
// test cases
})
專用測試 或 跳過測試
專用測試允許只測試指定的測試集或用例,只需在測試集或用例前加.only()
,示例如下:
describe(`Array`, function(){
describe.only(`#indexOf()`, function(){
...
})
})
跳過類似於 junit 的 @Ignore
, 用於跳過或忽略指定的測試集或用例,只需在測試集或用例前加.skip()
,示例如下:
describe(`Array`, function(){
describe.skip(`#indexOf()`, function(){
...
})
})
編輯器外掛
除了使用 Mocha 提供的命令列外,還可以使用 編輯器外掛 執行,目前支援了:
- TextMate
- JetBrains
- Wallaby.js
- Emacs
以JetBrains為例,JetBrain 為其 IDE 套件(IntelliJ IDEA, WebStorm等)提供了 NodeJS, 可以直接執行或除錯 mocha 測試用例。基本步驟:
- 安裝 NodeJS 外掛(如果沒有安裝的話,預設IntelliJ IDEA, WebStorm都已安裝):通過 Preferences > Plugins 查詢名為
NodeJS
的外掛並安裝; - 新增 mocha 測試。通過
Edit Configuration
新增Mocha
, 示例如下:
- 執行或除錯。支援按目錄、檔案、測試集、測試用例等執行或除錯,執行示例如下:
其它
以下列舉幾個基於 nodejs 的測試框架或工具,它們可用於 nodejs 或 瀏覽器端javascript 程式碼的測試,不在本文討論範圍,詳見官方文件。
Jasmine
A Behavior Driven Development JavaScript testing framework
Jasmine is a behavior-driven development framework for testing JavaScript code. It does not depend on any other JavaScript frameworks. It does not require a DOM. And it has a clean, obvious syntax so that you can easily write tests.
更多見 官方文件.
Karma Runner
To bring a productive testing environment to developers
The main goal for Karma is to bring a productive testing environment to developers. The environment being one where they don`t have to set up loads of configurations, but rather a place where developers can just write the code and get instant feedback from their tests.
更多見 官方文件.
工具
mocha 提供了測試的基本框架,在特定的場景下的測試還需要其它工具做以輔助,這個列舉幾個常用的工具。
SuperTest
SuperTest 提供對 HTTP 測試的高度抽象, 極大地簡化了基於 HTTP 的測試。
The motivation with this module is to provide a high-level abstraction for testing HTTP, while still allowing you to drop down to the lower-level API provided by super-agent.
安裝
$ npm install supertest --save-dev
使用示例
- 簡單的 HTTP 請求
var request = require(`supertest`);
describe(`GET /user`, function() {
it(`respond with json`, function(done) {
request(app)
.get(`/user`)
.set(`Accept`, `application/json`)
.expect(`Content-Type`, /json/)
.expect(200, done);
});
});
- 上傳檔案
request(app)
.post(`/`)
.field(`name`, `my awesome avatar`)
.attach(`avatar`, `test/fixtures/homeboy.jpg`)
// ..
- 修改響應頭或體
describe(`GET /user`, function() {
it(`user.name should be an case-insensitive match for "tobi"`, function(done) {
request(app)
.get(`/user`)
.set(`Accept`, `application/json`)
.expect(function(res) {
res.body.id = `some fixed id`;
res.body.name = res.body.name.toUpperCase();
})
.expect(200, {
id: `some fixed id`,
name: `TOBI`
}, done);
});
});
更多見 文件
Istanbul
程式碼覆蓋(Code coverage)是軟體測試中的一種度量,描述程式中原始碼被測試的比例和程度,所得比例稱為程式碼覆蓋率。它有四個測量維度:
- 行覆蓋率(line coverage):是否每一行都執行了
- 函式覆蓋率(function coverage):是否每個函式都呼叫了
- 分支覆蓋率(branch coverage):是否每個if程式碼塊都執行了
- 語句覆蓋率(statement coverage):是否每個語句都執行了
Istanbul 是 JavaScript 語言最流行的程式碼覆蓋率工具。
快速開始
- 安裝
$ npm install -g istanbul
- 執行
最簡單的方式:
$ cd /path/to/your/source/root
$ istanbul cover test.js
輸出執行結果:
..
test/app/util/result.test.js
should static create
should be success
should be static success
should be error
should be static error
299 passing (13s)
[mochawesome] Report saved to /opt/source/node_modules/.mochawesome-reports/index.html
=============================== Coverage summary ===============================
Statements : 92.9% ( 1505/1620 )
Branches : 85.42% ( 410/480 )
Functions : 94.33% ( 133/141 )
Lines : 93.01% ( 1504/1617 )
================================================================================
Done
這條命令同時還生成了一個 coverage
子目錄,其中的 coverage.json
檔案包含覆蓋率的原始資料,coverage/lcov-report
是可以在瀏覽器開啟的覆蓋率報告,如下圖所示:
模式
常見的測試模式包括TDD, BDD。
TDD (Test Driven Development)
測試驅動開發是敏捷開發中的一項核心實踐和技術,也是一種設計方法論。TDD的原理是在開發功能程式碼之前,先編寫單元測試用例程式碼,測試程式碼確定需要編寫什麼產品程式碼。TDD的基本思路就是通過測試來推動整個開發的進行,但測試驅動開發並不只是單純的測試工作,而是把需求分析,設計,質量控制量化的過程。整體流程如下:
以一個階乘的小程式為例。先寫出的測試用例,如下所示。此時執行肯定會報錯,因為被測試的程式還沒有寫。
var assert = require(`assert`),
factorial = require(`../index`);
suite(`Test`, function (){
suite(`#factorial()`, function (){
test(`equals 1 for sets of zero length`, function (){
assert.equal(1, factorial(0));
});
test(`equals 1 for sets of length one`, function (){
assert.equal(1, factorial(1));
});
test(`equals 2 for sets of length two`, function (){
assert.equal(2, factorial(2));
});
test(`equals 6 for sets of length three`, function (){
assert.equal(6, factorial(3));
});
});
});
開始寫階乘的邏輯,如下所示。
module.exports = function (n) {
if (n < 0) return NaN;
if (n === 0) return 1;
return n * factorial(n - 1);
};
此時再執行之前的測試用例,看其是否通過,如果全部通過則表示開發完成,如不通過則反覆修改被測試的邏輯,直到全部的通過為止。
BDD (Behavior Driven Development)
行為驅動開發是一種敏捷軟體開發的技術,它鼓勵軟體專案中的開發者、QA和非技術人員或商業參與者之間的協作。主要是從使用者的需求出發,強調系統行為。其最顯著的特徵是,通過編寫行為和規格說明來驅動軟體開發。行為和規格說明看上去與測試十分相似,但它們之前還是有顯著的不同。
還是以上面的階乘為例,BDD模式的測試用例如下:
var assert = require(`assert`),
factorial = require(`../index`);
describe(`Test`, function (){
before(function(){
// Stuff to do before the tests, like imports, what not
});
describe(`#factorial()`, function (){
it(`should return 1 when given 0`, function (){
factorial(0).should.equal(1);
});
it(`should return 1 when given 1`, function (){
factorial(1).should.equal(1);
});
it(`should return 2 when given 2`, function (){
factorial(2).should.equal(2);
});
it(`should return 6 when given 3`, function (){
factorial(3).should.equal(6);
});
});
after(function () {
// Anything after the tests have finished
});
});
從示例上看,BDD的測試用例與TDD最大的不同在於其措辭,BDD讀起來更像是一個句子。因此BDD的測試用例可作為開發者、QA和非技術人員或商業參與者之間的協同工具,對開發者來說,如果你可以非常流暢地讀測試用例,自然能夠寫出更好、更全面的測試。
TDD vs BDD
TDD和BDD本質和目標都是一致的。只是在實施方法上,進行了不同的探討來完善整個敏捷開發的體系。TDD的迭代反覆驗證是敏捷開發的保障,但沒有明確如何根據設計產生測試,並保障測試用例的質量,而BDD倡導大家都用簡潔的自然語言描述系統行為的理念,恰好彌補了測試用例(即系統行為)的準確性。
幾乎所有基於Nodejs的庫或應用都選擇了BDD, 至於為什麼,我還沒有搞明白。
斷言
目前比較流行的斷言庫包括:
- should.js BDD style shown throughout these docs
- expect.js expect() style assertions
- chai expect(), assert() and should style assertions
- better-assert c-style self-documenting assert()
- unexpected the extensible BDD assertion toolkit
這些斷言庫除了風格上略有不同外,實現在功能幾乎完全一致,可根據個人喜好或應用需求選擇。
相關文章
- 從 0 到 1 開展軟體測試
- 從理論到工具:帶你全面瞭解自動化測試框架框架
- 效能測試總結(一)---基礎理論篇
- web3從入門到實戰-理論篇Web
- 測試開發:從0到1學習如何測試API閘道器API
- 軟體測試理論(1)基礎理論
- 從測試策略到測試架構架構
- 從論文到測試:Facebook Detectron開源專案初探
- 90%的人都搞不懂的資料治理,從理論到實踐,都在這裡了
- 測試流程和理論--測試流程體系
- 測開新手:從0到1,自動化測試接入Jenkins學習Jenkins
- 軟體測試理論(2)自動化測試
- 測試開發進階:一文教你從0到1搞懂大資料測試!大資料
- 軟體測試基礎理論
- 從輸入 URL 到頁面載入完成(前端優化理論篇)前端優化
- DevOps 從理論到實踐指南dev
- 月薪20K的自動化測試:從0開始搭建測試體系,基礎篇
- 從0到1學習介面自動化測試必備知識!
- 系統測試-從研發到測試過程
- 從分散式一致性談到CAP理論、BASE理論分散式
- 軟體測試理論和實踐
- nodejs+koa2+mongodb 從0到1搭建自己的專案NodeJSMongoDB
- RocketMQ實戰系列從理論到實戰MQ
- 從理論到實踐 全面理解HTTP/2HTTP
- Docker最全教程——從理論到實戰(二)Docker
- Docker最全教程——從理論到實戰(一)Docker
- Docker最全教程——從理論到實戰(五)Docker
- Docker最全教程——從理論到實戰(四)Docker
- Docker最全教程——從理論到實戰(三)Docker
- Docker最全教程——從理論到實戰(六)Docker
- Docker最全教程——從理論到實戰(八)Docker
- Docker最全教程——從理論到實戰(七)Docker
- 從0到1構建美團壓測工具
- 【基礎】效能測試,從0到實戰(手把手教,非常實用)
- 從 0 到 1 開發壓力測試框架: Python 基礎,壓測框架開發框架Python
- 騰訊AI Lab 8篇論文入選,從0到1解讀語音互動能力AI
- <<從0到1學C++>> 第3篇 從結構到類的演變C++
- RocketMQ - 理論篇MQ