| name | py-testing |
| description | Design, implement, and repair Python test suites with pytest, fixtures, parametrization, mocks, fakes, property-based testing, async tests, integration tests, contract tests, coverage, flaky-test diagnosis, and CI quality gates. Use when adding tests, reviewing test architecture, reproducing bugs, validating migrations, or making Python behavior deterministic and regression-safe.
|
| compatibility | Agent Skills-compatible. pytest 9 supports Python 3.10+; inspect the project's pinned pytest and plugin versions before using newer APIs. |
| metadata | {"author":"stevenke1981","version":"2.0.0","last-reviewed":"2026-07-30"} |
Python 測試工程
目標與邊界
用此 skill 建立快速、穩定、能說明行為契約的測試。測試重點是防止有意義的回歸,不是追求單一 coverage 百分比或對每一行實作做鏡像斷言。
若任務主要是實作功能,仍以對應領域 skill 為主,py-testing 負責測試策略與品質閘門。
執行流程
- 先重現問題或行為:建立最小失敗案例,記錄輸入、環境與期望。
- 辨識測試層級:unit、component、integration、contract、end-to-end 或 performance。
- 選擇穩定邊界:優先測 public behavior;只有必要時才測內部細節。
- 控制非決定性:時間、亂數、網路、檔案、環境變數、locale、timezone 與並行。
- 建立代表性案例:正常、邊界、錯誤、空值、極端值、權限與故障注入。
- 執行最小到完整套件:先單一測試,再目錄,最後完整 CI matrix。
- 檢查測試品質:避免過度 mock、共享狀態、順序依賴與無條件重跑。
- 回報驗證範圍:列出執行命令、通過結果、未覆蓋環境與已知限制。
測試層級
| 層級 | 驗證內容 | 典型特性 |
|---|
| Unit | 純函式、domain rule、parser | 快、隔離、數量多 |
| Component | 一個模組或 service + fake boundary | 驗證協作但仍可控 |
| Integration | DB、filesystem、message broker、HTTP adapter | 真實依賴與 lifecycle |
| Contract | client/server、schema、provider adapter | 防止介面漂移 |
| End-to-end | 完整使用者流程 | 數量少、成本高 |
| Property-based | 不變量與廣泛輸入空間 | 找到人工案例之外的錯誤 |
| Performance | 延遲、吞吐、記憶體回歸 | 需穩定環境與統計方法 |
不要把所有測試都做成 E2E,也不要用 mock 讓 integration test 失去整合意義。
pytest 基本設定
[tool.pytest.ini_options]
addopts = ["-ra", "--strict-config", "--strict-markers"]
testpaths = ["tests"]
xfail_strict = true
markers = [
"integration: requires real infrastructure or service fixtures",
"slow: intentionally slower validation",
]
strict 模式可提早發現拼錯 marker 或設定。CI 不應依賴開發者本機自動載入的未知 plugin。
清楚的行為測試
import pytest
def calculate_discount(total: int, rate: float) -> int:
if total < 0:
raise ValueError("total must be non-negative")
if not 0.0 <= rate <= 1.0:
raise ValueError("rate must be between 0 and 1")
return round(total * rate)
@pytest.mark.parametrize(
("total", "rate", "expected"),
[
(1_000, 0.1, 100),
(0, 0.5, 0),
(999, 0.0, 0),
],
)
def test_calculate_discount(
total: int,
rate: float,
expected: int,
) -> None:
assert calculate_discount(total, rate) == expected
@pytest.mark.parametrize(
("total", "rate"),
[(-1, 0.1), (100, -), ()],
)
() -> :
pytest.raises(ValueError):
calculate_discount(total, rate)
斷言 observable outcome、exception type 與必要訊息,不要對每一步內部呼叫順序做脆弱斷言。
Fixture 設計
- fixture 只負責建立可重用測試資源,不隱藏主要 Arrange 行為。
- scope 越大,共享狀態與順序依賴風險越高。
- resource fixture 使用
yield 確保 teardown。
- factory fixture 適合產生多種資料,但要有明確型別。
- 使用
tmp_path、monkeypatch 與 framework 提供的 lifecycle fixture。
- 不在 import time 建立資料庫、client 或 event loop。
from collections.abc import Callable
from dataclasses import dataclass
import pytest
@dataclass(frozen=True, slots=True)
class User:
name: str
active: bool
@pytest.fixture
def user_factory() -> Callable[..., User]:
def make_user(
*,
name: str = "Alice",
active: bool = True,
) -> User:
return User(name=name, active=active)
return make_user
Mock、Fake 與 Stub
優先順序
- 純值與真實 domain object
- 小型 in-memory fake
- local test server / containerized dependency
- mock 外部邊界
規則:
- patch 使用物件被查找的位置,不是原始定義位置。
- mock clock、random、HTTP transport、email sender 等 boundary。
- 不 mock 被測函式自己的核心邏輯。
- 對重要 adapter 增加 contract test,防止 fake 與真實服務漂移。
- 使用 autospec/spec_set 可降低不存在屬性的假通過,但仍不能取代 integration test。
時間、亂數與環境
- 將 clock 與 random generator 注入,而不是到處呼叫全域函式。
- 測試固定 timezone 與 locale,並加入 DST/日期邊界案例。
- 使用 seeded randomness,但失敗時輸出 seed。
- 每個測試自行設定環境變數並復原。
- 不依賴目前工作目錄、使用者家目錄或測試執行順序。
Async 測試
- 使用與專案相容的 async pytest plugin 或測試 runner。
- 每個測試結束檢查未完成 task、未關閉 client 與 resource warning。
- 不用真實
sleep() 等待背景工作;使用 event、queue 或可控 clock。
- 測 cancellation、timeout、partial failure、slow consumer 與 graceful shutdown。
- 對 client disconnect 驗證上游工作真的取消。
import asyncio
import pytest
@pytest.mark.asyncio
async def test_worker_honors_cancellation() -> None:
started = asyncio.Event()
async def worker() -> None:
started.set()
await asyncio.Future()
task = asyncio.create_task(worker())
await started.wait()
task.cancel()
with pytest.raises(asyncio.CancelledError):
await task
plugin marker/mode 依鎖定版本設定,不要複製不相容的網路範例。
Property-based Testing
適合:
- parser/serializer round trip
- 排序、去重與集合不變量
- 金額、日期與區間邊界
- protocol frame 與 malformed input
- schema migration
範例不變量:
- encode 後 decode 等於原始資料。
- normalize 執行兩次等於一次。
- filter 不會增加列數。
- 聚合後總額與來源 reconciliation 一致。
保留 framework 回報的最小反例,並在修復後加入 regression case。
Integration 與 Contract Tests
- 使用 ephemeral database/schema、temporary directory 或受控 container。
- migration 從空資料庫與前一正式版本都要測。
- HTTP 測 status、headers、schema、auth、timeout 與 retry,而不只 body happy path。
- provider adapter 對 request/response mapping 建立 contract fixtures。
- 測試資料不得包含真實 token、個資或生產快照中的機密。
- live external test 應 opt-in,且有成本與速率限制。
Flaky Test 處理
不要直接加入 rerun 後忽略。
- 收集失敗 seed、順序、worker、OS、Python、時區與資源資訊。
- 單獨重跑並使用隨機順序重現。
- 檢查共享狀態、固定 port、真實時間、thread/task leak 與 eventual consistency。
- 修正同步條件或隔離資源。
- 若必須 quarantine,設定 owner、原因與到期日。
Coverage 與品質閘門
- 使用 branch coverage,但不要只追求單一百分比。
- 高風險 path、錯誤處理與權限邊界比簡單 getter 更重要。
- 新 bug 必須先有失敗 regression test。
- 可對核心純邏輯使用 mutation testing 評估斷言強度。
- 慢測試分類並定期執行,不要永久從 CI 消失。
常用命令:
pytest -q
pytest tests/unit -q
pytest -m "not slow" -q
pytest --cov=src --cov-branch --cov-report=term-missing
交付標準
- 測試驗證 public behavior 與重要不變量,不鏡像實作細節。
- 時間、亂數、網路、檔案與環境等非決定性已受控。
- fixture 有清楚 scope、型別與 teardown。
- mock 限於 boundary,重要 adapter 有 integration/contract test。
- async 測試涵蓋取消、逾時與資源關閉。
- flaky test 有根因處理,不以無限 rerun 掩蓋。
- CI 命令、marker、coverage 與支援 Python matrix 已記錄。
- 測試失敗能提供可重現的輸入與環境資訊。
延伸閱讀
版本相容性
| 環境 | 建議 |
|---|
| Python 3.14 | 穩定維護基準;確認 coverage、async 與 native plugin |
| Python 3.10–3.13 | 支援;依 pytest/plugin lockfile 選 API |
| pytest 9.x | Python 3.10+;升級時檢查 collection 與 plugin 相容性 |
| 舊 pytest | 先讀 changelog/deprecations,不直接套用 9.x 設定 |
| Preview Python | 只作 compatibility job,不取代穩定 CI |