[筆記] TDD/BDD and Test Double

R. H.
hobo engineer
Published in
10 min readJan 9, 2019

這篇筆記分幾個重點

  1. What’s TDD / BDD ?
  2. What’s test double?
  3. How to write Unit Test with Mocha in TDD/BDD style?

What’s TDD / BDD ?

首先簡單說明下這兩者

Test-driven Development (測試驅動開發):

是一種開發的流程。許多專案在開發時,通常會邊寫程式邊寫測試,或是先寫程式後寫測試,或是寫程式不寫測試。

TDD 則是「先寫測試再開發程式」。

最大的好處是開發者寫出測試時,就可以瞭解這一個元件/方法最後會怎麼使用,同時也釐清程式該怎麼設計。整個開發流程會在單元測試、撰寫程式、重構三者間不斷循環,是一種有效提升程式品質的開發方法。

一個重點是 TDD 是著重在開發者(工程師)身上,理想上皆由工程師負責。

Behavior-driven Development (行為驅動開發):

BDD 是 TDD 的進化版,除了一樣先寫測試再實作外,還要先寫規格,但是這份規格並不是單純的敘述軟體的功能,而是這份規格,是一份「可以執行的規格」,也就是其程式語法描述其極接近日常口語,相當簡單易懂,也可以執行。也就是說比起 TDD ,BDD 更可以讓非技術人員一起參與討論,能讓使用者、測試人員與開發人員,可以用一樣的方式來描述與了解需求。

此部分參考自:

What’s test double?

先介紹兩個測試常用術語:

SUT:System Under Test 或 Software Under Test的簡寫,代表待測程式。如果是單元測試,SUT就是一個function或method。
DOC:Depended-on Component(相依元件),是SUT執行的時候會使用到的元件。例如,有一個函數X如果執行失敗會寄送email,則email元件就是函數X的DOC。

而 Unit Test 有這5大原則

以上其中一個要點便是 外部相依性為零 ,簡單說就是只對受測的方法做測試,測試過程不可調用到網路、資料庫、其他相依方法等等。若受測的方法有調用到這些,可用 Test Double來替代。

Test Double 為假物件的統稱,主要有 Dummy、Fake、Stub、Mock 與 Spy 這幾個類別。而為什麼此篇筆記需記錄這個 , 原因是 TDD/BDD 有個重點是需撰寫 Unit Test 單元測試,而單元測試的原則便是要做到 隔離相依性 ,只對欲檢測的方法進行檢測。

How to write Unit Test with Mocha in TDD/BDD style?

要用 Node.js 來撰寫測試,最常見的是 Mocha+Chai 的組合(相關套件有很多種)。

Mocha 提供 TDD/BDD Style 的測試套件,Chai 則是斷言庫的函式。

以TDD來說,在 Mocha 的官方文件中有說明提供以下幾種方法

The TDD interface provides suite(), test(), suiteSetup(), suiteTeardown(), setup(), and teardown().

範例 :

而以 BDD 來說

The BDD interface provides describe(), context(), it(), specify(), before(), after(), beforeEach(), and afterEach().

範例 :

可以看出這兩者提供的方法其實大同小異,例如在 TDD 中是先以 Suite()來標註 SUT ,然後再用 Test() 來撰寫每項測試。

然而在 BDD 中是先以 Describe() 描述 Feature,然後再用 it() 描述每個 Scenario。

目前理解是,Mocha在 TDD 與 BDD 提供的方法上沒甚麼區別性,功能都差不多,應只是方便使用者區分要用 TDD Style 還是 BDD Style 。

Chai 的部分主要用來協助判斷測試 , 常用的語法有這三種

這三者在功能上沒甚麼差異,都是用來協助判斷的方法。但 Should 較不能客製化錯誤的回報訊息,其他兩者可以。

最後以一個小案例來當結尾 Github

這是個 Express + MongoDB 的 Rest API ,主要功能為 CRUD 。

Route 的部分

/GET
/GET/id
/POST
/UPDATE/id
/DELETE/id

Schema的部分

//Qnap schema definition
let QnaperSchema = new Schema(
{
name: { type: String, required: true },
job: { type: String, required: true },
age: { type: Number, required: true, min: 1 },
createdAt: { type: Date, default: Date.now },
},
{
versionKey: false
}
);
// Sets the createdAt parameter equal to the current time
QnaperSchema.pre('save', next => {
now = new Date();
if(!this.createdAt) {
this.createdAt = now;
}
next();
});

每個 Qnaper包含 name , job , age , createdAt 四個 fields ,其中 createdAt會自動產生。

功能的部分大致上說明就是這樣,細節的部分不提,來看主要 TDD/BDD 的部分。

若是以以上功能來撰寫 Mocha BDD Style 的測試 , 會是以以下風格

...... //匯入需要的 packagedescribe('Unit test CRUD', () => {beforeEach((done) => { //Before each test we empty the database
.....//每個測試前都會執行 beforeEach()
});
});
//test /GET
describe('/GET qnapers', () => {
it('it should GET all the qnapers', (done) => {
chai.request(server)
.get('/qnaper')
.end((err, res) => {
res.should.have.status(200); //接著以斷言式判斷
res.body.should.be.a('array');
res.body.length.should.be.eql(0);
done();
});
});
});
//test /Post
describe('/POST qnaper', () => {
it('it should POST a qnaper with only one field', (done) => {
......
});
});
it('it should POST a qnaper with all fields', (done) => {
......
});
....... //UPDATE DELETE ....
});

接著在 Terminal 下執行 Mocha 指令就會執行此測試,結果如下

BDD Style

而若是以 TDD Style 來撰寫,會是以下風格

//mocha --ui tdd...... //匯入需要的 packagesuite('Unit test CRUD', function() {.....suite('/GET qnapers', function() {test('it should GET all the qnapers', function() {chai.request(server)
.get('/qnaper')
.end((err, res) => {
res.should.have.status(200);
res.body.should.be.a('Array');
// console.log(res.body)
// console.log(typeof res.body)
// res.body.length.should.be.eql(1);
});
});
});
suite('/POST qnaper', function() {test('it should POST a qnaper with only one field', function() {
.....
});test('it should POST a qnaper with all fields', function() {
....
});
});
.....});

兩者在語法上差異其實不大,只是在結構上可是使用者需求自行調整為 BDD Style or TDD Style 。

比較要注意的是,若要以 Mocha TDD 的方法來執行測是,語法要改為

mocha --ui tdd

這樣才會以 TDD 的 Function 執行

結果如下

Conclusion

以 Mocha 來說 TDD 跟 BDD 其實沒有太大的分別,最主要應該就只是差在使用者怎麼去使用去撰寫而已。TDD 跟 BDD 的差異應該還是要回歸到最本質上來看,TDD 強調的是先寫測試再實作,而 BDD 則強調是 TDD 進化版,一樣先寫測試,但語法更接近口語,而且是以使用者角度出發,先有 User Story (或稱 Feature)再來撰寫後續測試。

而 Mocha 只是提供方法工具還協助我們完成測試撰寫而已,所以怎麼撰寫其實還是要看是用哪種開發方法而定。

而此結尾小案例較疏漏的地方是並未達到單元測試的 "隔離相依物件" ,在測試上為圖方便還是直接連接資料庫來操作了。實際上若須達到 Unit Test 還須搭配 Sinon(Node.js 的 Mock Package)來協助使用 Test Double 才是。

--

--