TDD is Awesome you should try it! (Tutorial With Example)

TDD 測試驅動開發超讚可以試試看(附實際操作範例)

為什麼 TDD

TDD 測試驅動開發是一種開發方法論,先寫測試再實踐代碼
  • 更有效率與目的的開發:撰寫程式前已規劃好「期望」
  • 重構程式碼時更為方便:重構程式前重要的片段已經被測試過,如果過程中出錯測試會指向哪個環節出錯
  • 程式都是可以被測試的:測試先於實踐代碼
  • 更容易實現 Atomic Commit:TDD 更容易劃清提交的界線,使每個提交只做一件事,盡可能的小旦完整
  • 更鬆散的耦合:容易撰寫鬆散耦合的程式碼
  • 測試成為文件:測試可以幫助開發者更好的描述程式碼的行為

陳舊代碼是那些有價值但你害怕改動的代碼,為什麼害怕改動?因為複雜、沒有記載、太久沒人碰過,TDD 目的便是打造一個工作流程能夠驗證程式碼的行為,讓開發者能夠更有信心的重構、更動程式碼。

TDD 規則與執行流程

  • 三個規則:

    1. 在撰寫一個單元測試前,不可撰寫任何產品程式
    2. 只撰寫剛好無法通過的單元測試,不能編譯也算無法通過
    3. 只撰寫剛好能通過當前測試的產品程式
  • 三個執行流程:

    • 紅:撰寫失敗測試
    • 綠:撰寫最低限度的代碼使測試通過
    • 重構:審視代碼

實際單元測試 TDD: FizzBuzz

一、前置準備

  • 開發環境:選擇一套熟悉的程式語言與測試框架,這裡我使用 Vite🔗 TypeScript 與 Vitest🔗 作為範例。
  • 規格文件:簡單版 FizzBuzz。
# FizzBuzz 規格文件:
1. 接受數字並輸出字串
2. 如果輸入的數字是 3 的倍數,輸出 "Fizz"
3. 如果輸入的數字是 5 的倍數,輸出 "Buzz"
4. 如果輸入的數字是 3 和 5 的倍數,輸出 "FizzBuzz"

二、紅燈

根據第一個需求:「輸入數字輸出字串」,我們可以先寫一個測試做為開端,並可以期望它會失敗,因為我們的實踐代碼還是空白:

test('輸入數字_輸出字串', () => {
expect(fizzbuzz(1)).toBe('1');
});
// 實踐代碼
export const fizzbuzz = (input) {};

三、綠燈

在撰寫實踐代碼後可以期望測試通過「輸入數字_輸出字串」測試:

export const fizzbuzz = (input: number): string => {
return input.toString();
};

四、重構

綠燈時就算是達成了產品需求,這時候代碼就算達成了需求但可能還是不夠完美,可以於該階段進行重構,如果沒有要重構則可以繼續重複紅燈、綠燈、重構的流程。

五、最終成果

最終經過 4 次紅燈、綠燈、重構的流程後代碼如下:

// 實踐代碼
export const fizzbuzz = (input: number): string => {
// 初始版本
// const isFizz = input % 3 === 0
// const isBuzz = input % 5 === 0
// const isFizzBuzz = isFizz && isBuzz
// if (isFizzBuzz) {
// return 'FizzBuzz'
// }
// if (isFizz) {
// return 'Fizz'
// }
// if (isBuzz) {
// return 'Buzz'
// }
// return input.toString()
// 重構後
const fizz = input % 3 === 0 ? 'Fizz' : '';
const buzz = input % 5 === 0 ? 'Buzz' : '';
const output = `${fizz}${buzz}`;
return output || input.toString();
};
// 單元測試代碼
test('輸入數字_輸出字串', () => {
expect(fizzbuzz(1)).toBe('1');
});
test('輸入3的倍數_輸出Fizz', () => {
expect(fizzbuzz(9)).toBe('Fizz');
});
test('輸入5的倍數_輸出Buzz', () => {
expect(fizzbuzz(10)).toBe('Buzz');
});
test('輸入3和5的倍數_輸出FizzBuzz', () => {
expect(fizzbuzz(15)).toBe('FizzBuzz');
});

總結

有良好的自動化測試作為文件可以替程式碼形成保護網,確保程式的行為被記載與驗證。

TDD 藉由測試主導開發流程

並非撰寫測試就是 TDD,以上實際撰寫過 TDD 就可以體會其目標並不在於撰寫測試(測試只是它的副作用)而是在於透過測試主導出良好的軟體!

TDD 著重測試行為(What)而非實踐過程(How)

撰寫測試於實踐代碼後,容易使測試與實踐代碼相互耦合,使用 TDD 強制我們從行為層面去審視需求,避免寫出著重在實踐過程的測試。

好測試的代碼與好代碼不謀而合

好測試的代碼具備相同的特質:高內聚低耦合、關注點分離……藉由 TDD 開發的程式自然的需要具備這些特質。

延伸閱讀