Python Pytest 單元測試

測試是確保程式碼品質與長期維護性的關鍵。雖然 Python 內建了 unittest 模組,但現代 Python 開發者更傾向於使用 Pytest。Pytest 以其簡潔的語法、功能強大的 Fixtures 以及豐富的擴展生態系,成為了 Python 測試領域的事實標準。

為什麼選擇 Pytest 而非 unittest?

  1. 語法簡潔:不需要繼承特定類別或使用物件導向模式,直接撰寫函式即可。
  2. 自然的斷言:直接使用 Python 內建的 assert 標誌,不需要記住 assertEqual, assertTrue 等專用屬性。
  3. 強大的 Fixture 系統:提供了一種優雅且具備依賴注入特色的方式來管理測試環境與資料。
  4. 豐富的插件:例如 pytest-mock (模擬對象)、pytest-cov (覆蓋率報告)、pytest-xdist (平行執行) 等。

安裝

pip install pytest

基礎約定

Pytest 會自動偵測當前目錄下的測試檔案。為了讓 Pytest 找到你的測試,請遵循以下命名約定:

  • 測試檔案名稱應以 test_*.py*_test.py 結尾。
  • 測試函式名稱應以 test_ 開頭。

簡單範例

# calculator.py
def add(a, b):
    return a + b

# test_calculator.py
import pytest
from calculator import add

def test_add():
    # 直接使用 assert,出錯時 Pytest 會提供詳細的變數差異比較
    assert add(1, 2) == 3
    assert add(-1, 1) == 0

def test_type_error():
    # 測試是否會如預期般拋出異常
    with pytest.raises(TypeError):
        add(1, "2")

執行測試(在終端機輸入):

pytest

核心功能:Fixtures

Fixture 是 Pytest 的精髓。它讓你將「準備工作 (Setup)」與「清理工作 (Teardown)」獨立出來,並透過參數注入的方式提供給需要的測試案例。

基本 Fixture 與作用域

import pytest

@pytest.fixture(scope="function") # 每個測試函式執行前都會重新執行一次
def db_connection():
    print("\n[Setup] 建立資料庫連線")
    conn = {"status": "connected"}
    yield conn # yield 之前是 Setup 邏輯

    # yield 之後是 Teardown (清理) 邏輯
    print("\n[Teardown] 關閉資料庫連線")

def test_query(db_connection):
    # db_connection 參數會自動接收到 yield 出來的值
    assert db_connection["status"] == "connected"

使用 conftest.py 共享設定

如果你有多個測試檔案都需要相同的 Fixture,可以將它們寫在根目錄的 conftest.py 中。Pytest 會自動載入該檔案,你不需要手動 import 即可在所有測試檔案中直接使用那些 Fixture。

參數化測試 (Parametrize)

當你需要用多組資料測試同一個演算法或邏輯時,parametrize 能極大減少重複代碼。

import pytest

@pytest.mark.parametrize("x, y, expected", [
    (1, 2, 3),
    (10, 20, 30),
    (-1, 1, 0),
    (0, 0, 0),
])
def test_addition(x, y, expected):
    assert x + y == expected

標記 (Markers)

你可以標注特定的測試案例,以便於分類或控制執行行為。

  • @pytest.mark.skip(reason="還沒開發完"):跳過此測試。
  • @pytest.mark.xfail:預期此測試會失敗(可用於標記已知的 Bug)。
  • 自定義標記
    @pytest.mark.slow
    def test_heavy_task():
        ...
    
    執行時可透過 pytest -m slow 只跑特定的測試。

常用指令技巧

  • pytest -v:Verbose 模式,顯示每個測試案例的完整名稱與結果。
  • pytest -s:允許在輸出中顯示 print() 的內容。
  • pytest -k "user":只執行名稱包含 "user" 的測試案例。
  • pytest --maxfail=2:在發生 2 次錯誤後立即停止執行(節省時間)。

總結

  • Pytest 的開發門檻低於 unittest,但上限更高。
  • 善用 assert 簡化斷言邏輯。
  • 學習 Fixtures 來打造專業的測試套件結構。
  • conftest.py 是組織跨檔案測試設定的關鍵。

對於任何實務的 Python 專案,建立良好的 Pytest 測試習慣不僅能減少線上 Bug,更能讓你勇敢地重構程式碼,因為測試就是你的最強後盾。