Loading...

「撰寫測試」已成為現代軟體開發的顯學。隨著軟體產品的規模越長越大,在不斷增加新功能、重構優化既有程式碼的過程,如何確保軟體既有功能不受影響,又能減少繁瑣的人工作業,靠的就是自動化測試。尤其當系統的業務邏輯龐大繁瑣,平時養成撰寫測試的好習慣更是保障軟體品質的關鍵。

開發團隊寫測試,通常有三種模式:

  1. 先寫測試再開發
  2. 開發完成再寫測試
  3. 無招勝有招——不寫測試(誤)

本文的重點就是第一種模式,先寫測試再開發,也就是常聽到的 TDD(Test-Driven Development)。也許你還沒有寫測試的經驗,希望一窺何謂測試撰寫;又或者你已經有撰寫測試的經驗,但對於 TDD 模式感到陌生。

本文將介紹如何進行 TDD,並以一個簡單的題目,盡量用具體且易懂的方式,來示範傳統開發模式和 TDD 開發模式在流程和思維上的差異。

8週業界專案實戰,模擬實務開發TDD流程

什麼是 TDD(Test-Driven Development)?

TDD(Test-Driven Development)是一種開發流程,中文是「測試驅動開發」。用一句白話形容,就是「先寫測試再開發」。先寫測試除了能確保測試程式的撰寫,還有一個好處:有助於在開發初期釐清程式介面如何設計

程式介面,或是常說的 API 介面,是內部封裝細節和外部元件的溝通橋樑。在實作時,我們通常會希望程式介面維持穩定,越少改動越好。但在開發初期憑空定義出來的介面,常常在開發完成實際使用時才發現不好用,導致介面需要頻繁改動。

測試程式的作用是「模擬外部如何使用目標程式,驗證目標程式的行為是否符合預期」。換句話說,在寫測試時,會去了解目標程式如何被使用,比起憑空定義介面,更有助於在實作目標程式之前釐清適合的介面設計,減少後續變動的次數。

沒有程式怎麼寫測試?——TDD 流程五步驟

你可能會有疑問:既然還沒有目標程式,那怎麼憑空寫測試?

下面這張圖很清楚地闡述 TDD 的運作,也就是所謂的「紅燈/綠燈/重構」循環(Red/Green/Refactor)


Wikipedia(圖片來源:

具體來說,TDD 流程可以分成五個步驟:

步驟一:選定一個功能,新增測試案例

  • 重點在於思考希望怎麼去使用目標程式,定義出更容易呼叫的 API 介面。
  • 這個步驟會寫好測試案例的程式,同時決定產品程式的 API 介面。
  • 但尚未實作 API 實際內容

步驟二:執行測試,得到 Failed(紅燈)

  • 由於還沒撰寫 API 實際內容,執行測試的結果自然是 failed。
  • 確保測試程式可執行,沒有語法錯誤等等。

步驟三:實作「夠用」的產品程式

  • 這個階段力求快速實作出功能邏輯,用「最低限度」通過測試案例即可。
  • 不求將程式碼優化一步到位。

步驟四:再次執行測試,得到 Passed(綠燈)

  • 確保產品程式的功能邏輯已經正確地得到實作。
  • 到此步驟,將完成一個可運作且正確的程式版本,包含產品程式和測試程式。

步驟五:重構程式

  • 優化程式碼,包含產品程式和測試程式(測試程式也是專案需維護的一部份)。
  • 提升程式的可讀性、可維護性、擴充性。
  • 同時確保每次修改後,執行測試皆能通過。

每個功能重複上述步驟,就是 TDD 的開發流程。

實戰示範

接下來會用同一個具體的程式題目,實際示範傳統模式(先開發再寫程式)和 TDD 模式在開發和寫測試的流程差別。

範例所用的語言工具:

  • Node.js 
  • 測試框架:Mocha
  • 斷言套件:Expect

範例題目

改編自 Coding Dojo(Dojo 的中文為「道場」)一個小型程式題目:員工報表管理系統(Employee Report)。

公司有一份資料結構,儲存了每個員工的姓名、到職日、性別:

<p>CODE: https://gist.github.com/ackent/bfa814e398d5e81920d848e3f6758c4e.js</p>

程式的目標是撰寫一個功能,可以撈出年資大於指定數字的資深員工清單。

傳統模式示範(先開發再寫程式)

直接開發

傳統模式就是初步規劃後,直接殺入開發,實作出一個 `getReportBySeniority()` 函數,可以接受「比較基準日」和「年資條件」兩個參數:

<p>CODE: https://gist.github.com/ackent/bf929ad84899531b6cf72da5be47e832.js</p>

嗯,除了「年資條件」,還接受「比較基準日」參數,開發者自我感覺 API 介面應該夠用了。

人工測試

開發完成後,通常會寫個簡單的程式來呼叫這個功能,確認程式執行結果如預期(也就是人工測試驗證):

<p>CODE: https://gist.github.com/ackent/1aa5ee9cafe693c8f594dba7b19f02a5.js</p>

執行結果:

<p>CODE: https://gist.github.com/ackent/8990b026addeb8331c71969047bfb410.js</p>

很好!成功撈出年資十年以上的員工報表,功能開發完成。

撰寫自動化測試

如果是不寫測試的狀況,到這邊大概就可以收工,準備進行下一個功能的開發。但我們是重視軟體品質的開發團隊,所以要繼續寫自動化測試,為 `getReportBySeniority()` 新增一個測試案例,並使用斷言庫(Assertion Library)檢驗回傳資料是否符合預期:

<p>CODE: https://gist.github.com/ackent/ec7439b5a6070599b82bed3c6e32cf0d.js</p>

執行測試結果:


完成測試程式,代表未來即使重構程式碼或增加新功能,都能透過自動化測試確保功能正確性,不用再像上面人工進行測試確認。

以上就是一個典型的傳統開發暨寫測試的流程。

TDD 模式示範

如果是 TDD 的模式呢?

步驟一:撰寫測試程式

先從測試案例切入:

<p>CODE: https://gist.github.com/ackent/55700a831c73f040ee27ee71440f91eb.js</p>

思考如果是外部元件,在不管實作細節的前提,會怎麼呼叫這個功能。以「撈出十年資深員工」的使用情境,可以定義出以下 API 介面:

<p>CODE: https://gist.github.com/ackent/6251b223a3756dca896d35856806ccc1.js</p>

這時候是站在使用者角度,容易聯想到有資深就有資淺,如果之後還想「撈出五年內的資淺員工」呢?

<p>CODE: https://gist.github.com/ackent/64b08848d7fbb587be881ea7f40bee4f.js</p>

原本所設想的 API 介面顯然無法支援,至少需要再增加支援「年資大於」或「年資小於」的參數控制。最後完成的測試程式如下:

<p>CODE: https://gist.github.com/ackent/1fca62b5239b9623702de116bd19bb35.js</p>

要注意,這時候還沒有實作 API 的功能邏輯,只決定了介面:

<p>CODE: https://gist.github.com/ackent/59c35e60f3b44d797f04f804290a3341.js</p>


步驟二:執行紅燈測試

由於還沒實作 API 功能,理所當然得到紅燈:


步驟三:實作「夠用」的產品程式

實作 `getReportBySeniority()` 的內容。記得在這個步驟不要花太多力氣在雕琢程式碼,而是專注在功能邏輯的實作。

<p>CODE: https://gist.github.com/ackent/eccd5e6f88f6538ab55a1900ef076f7a.js</p>

步驟四:執行綠燈測試

如果得到紅燈表示某處邏輯有錯誤,這個步驟必須修正到測試通過,確保功能邏輯的正確性。

至此,一個可運作且正確的程式版本已經完成,涵蓋產品程式和測試程式。


步驟五:重構

為了系統長遠發展的維護性,適當的重構是需要的。例如利用 `Array.prototype.filter()` 對 `getReportBySeniority()` 函數進行程式碼的簡化:

<p>CODE: https://gist.github.com/ackent/d5980e869dcab808fc3bcb0087df7c46.js</p>

由於測試程式早已在前面步驟完成,可以放心重構,即使不慎改壞程式也可以透過自動化測試立即發現。

結語

經由上面的手把手示範,對於如何撰寫測試、如何用 TDD 模式開發,相信能有初步的概念,也感受到傳統模式和 TDD 流程在思維上的差異。

傳統模式容易遇到以下問題:

  • 初期憑空設計介面,容易不適用,需要後續多次修改。
  • 後期撰寫測試,容易專注於已實作功能的範疇,而少了對其他使用者情境的聯想。
  • 容易因為專案時程或資源不足而犧牲測試程式的撰寫。

TDD 模式不僅在最初就確保測試程式的撰寫,相較於傳統模式,TDD 模式在一開始從使用方觀點切入,更容易在初期定義出更貼近使用方的介面。

理論上撰寫的測試案例越多,測試覆蓋率越高,代表對系統的信心度越高。然而專案時程和人力往往有限,而潛在的使用者情境案例可能無窮無盡。專案開發永遠是一個權衡(trade-off)的過程,在撰寫測試案例時,應該盡量選擇效益最大的測試案例撰寫,例如發生頻率最高的使用情境、已知一旦出錯將產生重大損失的案例等,然後在專案行有餘力時,持續補足其他 corner case,讓系統的品質確保更加穩固。

三分鐘小測驗,了解你可以從哪開始進階軟體開發

參考資料

成為企業渴求的程式人才!

在家學會 JavaScript 網路開發

全新「全端 Web App 開發」課程,給你看得見的學習成效!
超過 90% 轉職成功,400 位來自亞洲各國的 ALPHA Camp 校友,畢業後達成轉職、創業、出國工作的夢想!

探索「全端 Web App 開發」課程

給期待創新改變的你

前端x後端x全端 完整工程師技能樹

90% 學生轉職成功,職涯競爭力更上層樓
最專業的「全端 Web App 開發」課程,上班族邊工作也能同時培養第二專長!

加入 ALPHA Camp 學程式開發

學期一|程式設計入門

零基礎也學得會的程式入門課!

開始學帶得走的技能,為自己未來的成長鋪路

學期二|JavaScript 完整前端基礎

系統化學習 JavaScript

實作打好前端基礎,成為扎實的網頁開發者

今年最後機會,程式入門12月班 倒數5天
馬上報名