跳轉至

建立 2026-10-11 更新 2026-10-11

寫出好測試

好測試的標準是:失敗時能立刻看出哪個行為壞了、執行快、彼此獨立、每次結果都一樣,而且重構程式時不需要跟著大改。本頁整理讓測試達到這些標準的寫法,包含 AAA 結構、命名、測試金字塔、測試驅動開發(TDD),以及替舊程式補測試的方法。

用 AAA 結構組織每個測試

AAA 指 Arrange(準備)、Act(執行)、Assert(驗證)。每個測試都依這三段排列,讀的人就能快速看懂:

def test_vip_gets_extra_discount():
    # Arrange:準備資料
    cart = Cart(owner=User(vip=True))
    cart.add("keyboard", price=2000)

    # Act:執行要測的行為(通常只有一行)
    total = cart.checkout()

    # Assert:驗證結果
    assert total == 1800

Arrange 太長時,就是把它抽成 fixture 的訊號。

測試名稱要描述行為

測試失敗時,第一個看到的就是名稱。好名稱說明「在什麼情況下,應該發生什麼」:

不好的名稱 好的名稱
test_checkout test_vip_gets_extra_discount
test_discount_2 test_discount_over_100_percent_raises_value_error
test_bug_fix test_empty_cart_total_is_zero

名稱長沒關係,測試函式不會被其他程式呼叫。

測試行為,不要測試實作細節

測試應該驗證「對外的結果」,例如回傳值、寫入的檔案、送出的請求,而不是內部用了哪個私有函式或變數。只驗證行為的測試,在重構內部寫法時不需要修改,才能真正保護重構。

測試金字塔:各種測試的比例

類型 範圍 速度 建議數量
單元測試(unit test) 單一函式或類別,外部依賴用替身 毫秒級 最多
整合測試(integration test) 多個元件一起,例如程式+真的資料庫 秒級 中等
端對端測試(end-to-end test) 從使用者操作到整個系統 慢 少量,只測最關鍵的流程

pytest 三種都能寫。用 marker 把慢的整合測試與端對端測試分開,日常開發只跑單元測試,CI 再全部跑。

讓程式容易測試:依賴注入

程式在函式內部直接建立外部連線時,測試只能靠 patch:

def send_welcome(user):
    client = SmtpClient("smtp.example.com")   # 寫死在函式內
    client.send(user.email, "歡迎加入")

改成把依賴當參數傳入,測試就能直接給一個假物件:

def send_welcome(user, client):
    client.send(user.email, "歡迎加入")


def test_send_welcome():
    sent = []

    class FakeClient:
        def send(self, to, body):
            sent.append((to, body))

    send_welcome(User(email="amy@example.com"), FakeClient())
    assert sent == [("amy@example.com", "歡迎加入")]

這就是依賴注入(dependency injection)。「難寫測試」常常是程式設計可以改善的訊號。

測試驅動開發(TDD)

TDD(Test-Driven Development)是先寫測試、再寫程式的開發方式,以三個步驟循環:

  1. 紅燈(Red):先寫一個描述新功能的測試,執行它,確認它失敗。
  2. 綠燈(Green):寫出剛好能讓測試通過的程式碼。
  3. 重構(Refactor):在測試保護下整理程式碼,測試必須維持通過。

先確認測試會失敗很重要:一個從來沒失敗過的測試,可能根本沒測到東西。修 bug 時也適用同樣流程:先寫一個能重現 bug 的失敗測試,修好後它會通過,並從此防止同一個 bug 再出現。

替沒有測試的舊程式補測試

舊程式通常沒有考慮到可測試性,建議的順序:

  1. 先寫特徵測試(characterization test):記錄程式「目前的實際行為」,即使那不一定是正確行為,目的是在修改時發現意外的變化。
  2. 從最常修改、最容易出錯的部分開始,不要試圖一次補完。
  3. 有了測試保護後,再逐步重構成容易測試的結構,例如把外部依賴抽出來。

推薦影音

Test-Driven Development In Python(ArjanCodes)

ArjanCodes 用 Python 與 pytest 示範紅燈、綠燈、重構的 TDD 循環,約 15 分鐘,對應本頁「測試驅動開發」一節。

How To Write Unit Tests For Existing Python Code(ArjanCodes)

兩集系列的第一集,示範如何替一段原本沒有測試的 Python 程式逐步補上單元測試,對應本頁「替沒有測試的舊程式補測試」。第二集可在 YouTube 觀看。