🌟 Lynn's Blog
所有文章
Collections
測試驅動開發

開始在 JavaScript 中使用 TDD 設計模式!

測試驅動開發

TDD (Test-driven Development,測試驅動開發) 是一種開發技巧,透過在實作前先寫測試,幫助實作者更早的想清楚做一件事情想要達成的效果是什麼。

Why TDD?

那麼,為什麼我們需要 TDD?

首先我們需要回答:為什麼我們需要測試?

在維護專案時,我們會遇到一些問題,而寫測試正好能幫助我們解決這些煩惱:

  • 難以閱讀的前人程式碼 → 只要查看測試情境就可以了解程式碼的用途
  • 想要重構卻害怕改壞的義大利麵程式碼 → 測試可以確保重構後的程式碼仍然符合原本的需求
  • 做了新功能,卻發生改 A 壞 B 的憾事 → 測試可以確保新功能不會影響到原本的功能

而透過引入 TDD 方法,我們可以獲得更多好處,包含:

  • 在實作前就先想清楚要做什麼、影響範圍為何
  • 在開發前期就獲得測試的回饋,幫助減輕心理壓力

TDD 循環

TDD 方法的循環為:寫一個測試 → 盡快讓測試通過 → 重構出簡潔的設計。

假設我們想要寫一個用來執行加法的函式,接收兩個參數,並回傳兩者相加的結果,那麼我們可以透過以下步驟來實作:

寫一個測試(write a test,紅燈)

我們在心裡假設一個 add 函式,它會回傳兩個參數相加的結果,那麼這個函式的測試應該會長這樣:

add.test.js
describe('add', () => {
it('should return 3 when 1 + 2', () => {
expect(add(1, 2)).toBe(3)
})
})

盡快讓測試通過(make it run,綠燈)

接著我們開始想,應該要如何實作 add 函式,讓這個測試盡快通過呢?

add.js
function add(a, b) {
return 3
}

如果要讓測試盡快通過,最快的方法就是直接回傳一個和測試結果相同的值,這麼一來,這個測試就會直接通過,這時我們還不需要在 add 函式裡添加任何邏輯。

重構出簡潔的設計(make it right,重構)

我們看到在上一個步驟中,我們讓 add 函式直接回傳了一個固定的值,這樣的實作顯然不符合邏輯,但我們要抑制直接將函式改為回傳 a + b 的衝動,這樣太不 TDD 了。

我們要如何使用更 TDD 的方法來達成我們心裡的期待?
這邊我們引入另一個 TDD 的概念:三角定位法,三角定位法是指在實作時,我們可以透過多個測試情境來驗證我們的程式碼是否正確。
三角定位的來源是一個數學原理,當我們有兩個探測器時,就可以確定目標物體的位置,這個概念就很像我們寫多個測試情境驗證程式碼是否真的可用。

例如對於 add 函式,我們可以再寫一個測試情境:

add.test.js
describe('add', () => {
it('should return 3 when 1 + 2', () => {
expect(add(1, 2)).toBe(3)
})
it('should return 5 when 2 + 3', () => {
expect(add(2, 3)).toBe(5)
})
})

這麼一來,原本的 add 函式因為永遠都會回傳 3,所以它注定失敗,這時我們就可以開始思考如何重寫 add 函式,讓這兩個測試情境都通過。

add.js
function add(a, b) {
if (a === 1 && b === 2) {
return 3
}
return 5
}

身為軟體工程師,應該不會容忍這麼醜的程式碼,這時我們可以利用 TDD 提出的另外一種方法繼續重構程式碼:消除重複
重複其實就存在在測試和實作中,在測試情境中我們寫死了 1 和 2 作為參數,而在實作中我們也寫死了這兩個參數的結果,因此這樣的重複是可以被消除的。

add.js
function add(a, b) {
return a + b
}

透過把 add 函式的程式碼修改成這樣,我們就不需要寫兩次 1 和 2 了,這樣就算是消除了重複,至此我們就完成了 TDD 的整個循環。

TDD 模式

回顧 TDD 的循環,你或許會質疑:這樣的寫法真的有必要嗎?不過就是一個簡單的加法函式,為什麼要大費周章把每一步切的這麼細?我大可以在寫完測試後直接實作相加的邏輯,或是根本不用寫測試,因為這個函式的功能實在太簡單了!

這樣的想法其實很常見,在這麼簡單的例子裡,採取這麼細碎的步驟或許沒有必要,但是 TDD 是一種思考模式,我們需要準備好這樣思考,以讓我們面對更複雜的功能。

在接下來的文章裡,我會介紹更多 TDD 的觀念和應用,相信我,在面對複雜情況時,TDD 會是你最好的朋友。