iT邦幫忙

2025 iThome 鐵人賽

DAY 11
0
Modern Web

React TDD 實戰:用 Vitest 打造可靠的前端應用系列 第 12

Day 11 - Kata 入門與環境準備

  • 分享至 

  • xImage
  •  

今天會很不一樣

「老闆,網站上的年份顯示怪怪的...」

週一早上,你剛泡好咖啡,專案經理就衝進來。原來是客戶的歷史文物展覽網站,要求用羅馬數字顯示年代,結果顯示出來的東西讓人看不懂。

「MMXXIII 應該是 2023 年吧?」你心想,「但網站顯示的是 MMXVIII...」

這就是我們接下來要解決的問題:如何用 TDD 的方式,一步步建立一個可靠的羅馬數字轉換器。

什麼是 Code Kata?

就像武術的「型」或音樂的「練習曲」,Code Kata 是程式設計師用來磨練技巧的練習題。Roman Numeral Kata 是最經典的 TDD 練習之一。

Kata 的起源與意義

「Kata」源自日文,原意是武術中重複練習的基本動作組合。在程式設計領域,Code Kata 指的是短小且重複練習的程式設計問題,目的是透過反覆練習來培養直覺和肌肉記憶。

就像鋼琴家每天練習音階、武術家每天練習基本動作一樣,程式設計師也需要透過 Kata 來保持和提升自己的技能。每次練習同一個 Kata,你都會發現新的細節和改進空間。

為什麼選擇 Roman Numeral Kata?

這個 Kata 有許多優點,讓它成為 TDD 學習的完美選擇:

  • 規則明確:羅馬數字的轉換規則是固定的,沒有模糊地帶
  • 複雜度漸進:從簡單的 1=I 開始,逐步增加到減法規則
  • 完美展示 TDD:每個步驟都能清楚看到紅-綠-重構的循環
  • 跨語言一致:三個框架可以用相同的邏輯實作,便於對照學習
  • 實用性高:在真實專案中確實會遇到類似的轉換需求

30 天旅程進度 📍

第一階段:打好基礎(Day 1-10)✅
第二階段:挑戰練習(Day 11-17)
├── Day 11:入門與環境準備 ⬅️ 我們在這裡
├── Day 12:基礎符號轉換
├── Day 13:擴展到百位數
├── Day 14:完整範圍
├── Day 15:反向轉換
├── Day 16:效能優化
└── Day 17:程式碼整理與回顧
第三階段:後續章節...

認識羅馬數字

羅馬數字系統是古羅馬人發展的數字表示法,至今仍在許多場合使用,如鐘錶、建築物年份、書籍章節編號等。

基本符號

羅馬數字的七個基本符號:

符號 數值 記憶方法
I 1 一根手指
V 5 五根手指張開的形狀
X 10 兩個 V 交叉
L 50 拉丁文 "Quinquaginta" 的第一個字母
C 100 拉丁文 "Centum"(百)的第一個字母
D 500 拉丁文 "Quingenti" 的簡化
M 1000 拉丁文 "Mille"(千)的第一個字母

組合規則詳解

羅馬數字的組合遵循三個基本規則:

  1. 加法規則:相同符號連續出現,數值相加

    • III = I + I + I = 3
    • XXX = X + X + X = 30
    • CC = C + C = 200
  2. 左加右加規則:小數字在大數字右邊,數值相加

    • VI = V + I = 5 + 1 = 6
    • XII = X + I + I = 10 + 1 + 1 = 12
    • LX = L + X = 50 + 10 = 60
  3. 減法規則:特定的小數字在大數字左邊,數值相減

    • IV = V - I = 5 - 1 = 4
    • IX = X - I = 10 - 1 = 9
    • XL = L - X = 50 - 10 = 40
    • XC = C - X = 100 - 10 = 90
    • CD = D - C = 500 - 100 = 400
    • CM = M - C = 1000 - 100 = 900

重要限制

  • 同一符號最多連續出現三次(IIII 是錯誤的,應該寫成 IV)
  • 只有 I、X、C 可以作為減數
  • 減數只能是被減數的 1/10 或 1/5

設定專案結構

建立 src/roman/romanNumerals.ts

// 空白檔案,準備開始 TDD

建立 tests/day11/roman-numerals.test.ts

import { describe, it, expect } from 'vitest'

describe('Roman Numerals Converter', () => {
  it('convertsOneToI', () => {
    // 第一個測試,還沒有實作
    expect(toRoman(1)).toBe('I')
  })
})

第一個紅燈 🔴

執行測試:

npm test tests/day11

錯誤訊息:

ReferenceError: toRoman is not defined

完美!這就是我們要的紅燈。

最小實作讓測試通過 🟢

更新 src/roman/romanNumerals.ts

export function toRoman(num: number): string {
  return 'I'
}

更新測試檔案:

import { describe, it, expect } from 'vitest'
import { toRoman } from '../../src/roman/romanNumerals'

describe('Roman Numerals Converter', () => {
  it('convertsOneToI', () => {
    expect(toRoman(1)).toBe('I')
  })
})

測試通過了!

第二個測試

加入第二個測試:

it('convertsTwoToII', () => {
  expect(toRoman(2)).toBe('II')
})

測試失敗(紅燈 🔴)。

更新實作:

export function toRoman(num: number): string {
  if (num === 1) return 'I'
  if (num === 2) return 'II'
  return ''
}

測試通過(綠燈 🟢)!

第三個測試與重構

加入第三個測試:

it('convertsThreeToIII', () => {
  expect(toRoman(3)).toBe('III')
})

這時候我們發現模式了。重構時間!

export function toRoman(num: number): string {
  let result = ''
  for (let i = 0; i < num; i++) {
    result += 'I'
  }
  return result
}

所有測試都通過!這就是 TDD 的節奏:

  1. 🔴 寫一個失敗的測試
  2. 🟢 用最簡單的方式讓它通過
  3. 🔵 重構程式碼

今天的完整程式碼

src/roman/romanNumerals.ts

export function toRoman(num: number): string {
  let result = ''
  for (let i = 0; i < num; i++) {
    result += 'I'
  }
  return result
}

tests/day11/roman-numerals.test.ts

import { describe, it, expect } from 'vitest'
import { toRoman } from '../../src/roman/romanNumerals'

describe('Roman Numerals Converter', () => {
  it('convertsOneToI', () => {
    expect(toRoman(1)).toBe('I')
  })

  it('convertsTwoToII', () => {
    expect(toRoman(2)).toBe('II')
  })

  it('convertsThreeToIII', () => {
    expect(toRoman(3)).toBe('III')
  })
})

Kata 練習的心法

TDD 在 Kata 中的體現

Kata 練習是體驗 TDD 精髓的最佳方式。在這個過程中,你會深刻感受到測試驅動開發的核心理念:

  1. 小步前進:每次只處理一個簡單的案例

    • 不要想著一次解決所有問題
    • 專注於當下這一個測試案例
    • 讓測試告訴你下一步該做什麼
  2. 保持節奏:嚴格遵守紅-綠-重構的循環

    • 紅:先寫測試,看到失敗
    • 綠:用最簡單的方式讓測試通過
    • 重構:在測試保護下改善程式碼
  3. 相信過程:看似簡單的開始會帶來複雜的解決方案

    • 不要擔心一開始的實作太簡陋
    • 相信透過重構會逐步演化出好的設計
    • 測試會引導你找到正確的抽象
  4. 享受旅程:重點不是目的地,而是練習的過程

    • 專注於每一步的學習
    • 注意自己的思考過程
    • 反思每次重構的決策

常見的 Kata 練習錯誤

初學者在練習 Kata 時經常犯這些錯誤:

過度設計:一開始就想設計完美的架構
簡單開始:從最簡單的實作開始,讓測試引導設計

跳步驟:想要一次處理多個測試案例
單步進行:一次只專注一個測試

忽略重構:只關心讓測試通過,不整理程式碼
持續重構:在每個綠燈後檢視是否需要重構

完美主義:追求一次到位的完美解法
漸進改善:接受不完美,透過循環逐步改善

🤔 思考練習

在繼續之前,想想這些問題,這些思考會幫助你更深入理解即將面臨的挑戰:

立即挑戰

  • 如何處理 4(IV)? 這是第一個減法規則的案例
  • 如何處理 5(V)? 需要引入新的符號
  • 如何處理 6(VI)? 又回到加法規則

設計思考

  • 現在的實作能擴展到處理更大的數字嗎? 目前的迴圈方式有什麼限制?
  • 如何保持程式碼的簡潔性? 隨著規則增加,程式碼會變得複雜嗎?
  • 測試的順序重要嗎? 應該按照什麼順序添加測試案例?

深度思考

  • 什麼時候該重構? 如何判斷程式碼需要重構?
  • 如何避免過度工程? 在沒有測試需求時,不要添加不必要的功能
  • TDD 的節奏感如何培養? 如何在速度和品質之間找到平衡?

實作策略

思考一下,面對即將到來的複雜性,你會選擇:

  • 繼續擴展 if-else 條件?
  • 使用查表(lookup table)方式?
  • 採用數學計算方式?
  • 還是有其他更優雅的解法?

這些問題沒有標準答案,重要的是讓測試引導你找到最適合的解決方案。

重點回顧

今天我們學到了:

  • ✅ Code Kata 是磨練 TDD 技巧的練習
  • ✅ Roman Numeral Kata 的基本規則
  • ✅ 從最簡單的案例開始(1, 2, 3)
  • ✅ 保持紅-綠-重構的節奏
  • ✅ 小步前進的重要性

實戰小練習 🏃

在結束今天的學習前,試試這個小挑戰:

// 挑戰:不看上面的程式碼,自己寫出 toRoman 函數
// 提示:只需要處理 1, 2, 3 即可
function toRoman(num: number): string {
  // 你的程式碼
}

動手寫寫看,體驗 TDD 的第一步!

今日總結 🎯

今天是我們 Kata 練習的起點,我們:

  • 🏃 開始了第一個 TDD Kata 練習
  • 📖 了解了羅馬數字的基本規則
  • 🔴🟢🔵 體驗了完整的 TDD 循環
  • 💡 學會了從最簡單的案例開始

記住:TDD 不是一蹴可幾的,而是透過不斷練習培養的技能。就像學習樂器一樣,每天的練習都會讓你進步一點點。

明天,我們將進入更有趣的挑戰!


上一篇
Day 10 - 重構與測試:讓程式碼持續進化 🔧
系列文
React TDD 實戰:用 Vitest 打造可靠的前端應用12
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言