iT邦幫忙

2025 iThome 鐵人賽

DAY 12
0
Software Development

Python pytest TDD 實戰:從零開始的測試驅動開發系列 第 12

Day 12 - 基礎符號轉換(1-10) 🔢

  • 分享至 

  • xImage
  •  

昨天成功處理了 1、2、3,但 4 輸出 "IIII" 而非 "IV"。今天用 TDD 處理羅馬數字的減法規則。

羅馬數字減法規則 ⚖️

核心規則

  • 小數字在大數字前面時做減法
  • IV = V - I = 5 - 1 = 4
  • IX = X - I = 10 - 1 = 9

處理數字 4:第一個減法規則 🎯

建立 tests/day12/test_roman_converter_4.py

from src.roman.converter import to_roman

def test_convert_4():
    assert to_roman(4) == "IV"

測試失敗 🔴:Expected: "IV", Received: "IIII"

更新 src/roman/converter.py

def to_roman(number: int) -> str:
    if number <= 0:
        raise ValueError("Number must be positive")
    
    if number == 4:
        return "IV"
    
    result = ""
    for _ in range(number):
        result += "I"
    
    return result

測試通過 🟢

處理數字 5:V 的登場 ✨

建立 tests/day12/test_roman_converter_5.py

from src.roman.converter import to_roman

def test_convert_5():
    assert to_roman(5) == "V"

測試失敗 🔴

更新 src/roman/converter.py

def to_roman(number: int) -> str:
    if number <= 0:
        raise ValueError("Number must be positive")
    
    result = ""
    
    if number >= 5:
        result += "V"
        number -= 5
    
    if number >= 4:
        result += "IV"
        number -= 4
    
    for _ in range(number):
        result += "I"
    
    return result

處理 6-8:V 系列測試 📈

建立 tests/day12/test_roman_converter_6_7_8.py

from src.roman.converter import to_roman

def test_convert_6():
    assert to_roman(6) == "VI"
def test_convert_7():
    assert to_roman(7) == "VII"
def test_convert_8():
    assert to_roman(8) == "VIII"

測試通過 ✅

挑戰數字 9:第二個減法規則 🎯

建立 tests/day12/test_roman_converter_9.py

from src.roman.converter import to_roman

def test_convert_9():
    assert to_roman(9) == "IX"

測試失敗 🔴:輸出 "VIIII" 而非 "IX"

更新 src/roman/converter.py

def to_roman(number: int) -> str:
    if number <= 0:
        raise ValueError("Number must be positive")
    
    result = ""
    
    if number >= 9:
        result += "IX"
        number -= 9
    
    if number >= 5:
        result += "V"
        number -= 5
    
    if number >= 4:
        result += "IV"
        number -= 4
    
    for _ in range(number):
        result += "I"
    
    return result

處理數字 10 🔟

建立 tests/day12/test_roman_converter_10.py

from src.roman.converter import to_roman

def test_convert_10():
    assert to_roman(10) == "X"

更新 src/roman/converter.py

def to_roman(number: int) -> str:
    if number <= 0:
        raise ValueError("Number must be positive")
    
    mappings = [
        (10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I")
    ]
    
    result = ""
    
    for value, symbol in mappings:
        while number >= value:
            result += symbol
            number -= value
    
    return result

測試通過! 🟢

最終重構:映射表模式 🎨

重構 成映射表形式(同上)。

測試通過! ✅

TDD 學習重點 💡

透過 TDD,我們發現羅馬數字的重要模式:

  • 大數字優先:先處理大數字(10 → 5 → 1)
  • 減法規則優先:4 和 9 要特別處理
  • 貪婪算法:每次選擇最大可能符號

完整測試套件 🧪

建立 tests/day12/test_roman_converter_complete.py

import pytest
from src.roman.converter import to_roman

def test_convert_1_to_10():
    expected = [
        (1, "I"), (2, "II"), (3, "III"), (4, "IV"), (5, "V"),
        (6, "VI"), (7, "VII"), (8, "VIII"), (9, "IX"), (10, "X")
    ]
    
    for number, roman in expected:
        assert to_roman(number) == roman

def test_invalid_numbers():
    with pytest.raises(ValueError):
        to_roman(0)
    with pytest.raises(ValueError):
        to_roman(-1)

完整程式碼 📋

完整實作 src/roman/converter.py

def to_roman(number: int) -> str:
    """
    將阿拉伯數字轉換為羅馬數字
    
    Args:
        number: 要轉換的正整數
    
    Returns:
        對應的羅馬數字字串
    
    Raises:
        ValueError: 當輸入非正整數時
    """
    if number <= 0:
        raise ValueError("Number must be positive")
    
    mappings = [
        (10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I")
    ]
    
    result = ""
    
    for value, symbol in mappings:
        while number >= value:
            result += symbol
            number -= value
    
    return result

測試驗證 ✅

執行測試:

pytest tests/day12/ -v

所有測試通過!我們有了處理 1-10 的羅馬數字轉換器。

今天學到的重點 📝

  • 減法規則:IV = 4, IX = 9
  • TDD 循環:紅-綠-重構節奏
  • 映射表模式:簡化條件邏輯
  • 大數字優先:貪婪算法概念
  • 測試驅動設計:測試指導實作

今日小挑戰 🏆

試著思考以下問題:

  1. 為什麼選擇映射表模式?有什麼優點?
  2. 如果要處理 0 或負數,該如何修改?
  3. 映射表的順序為什麼重要?

重點回顧 🎁

今天我們成功實作了 1-10 的羅馬數字轉換,從最簡單的 if-else 開始,逐步演進到優雅的映射表模式。這就是 TDD 的魅力 - 讓我們的程式碼隨著測試逐步進化。

每個測試都像是一個小小的里程碑,紅燈告訴我們方向,綠燈確認我們走對了路,重構讓我們的程式碼更加優雅。這就是 TDD 的節奏感!

今天的 TDD 之旅就到這裡,明天我們繼續深入探索! 🚀


上一篇
Day 11 - Kata 介紹與設置 🎯
下一篇
Day 13 - 擴展到百位數(11-100) 💯
系列文
Python pytest TDD 實戰:從零開始的測試驅動開發15
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言