claude-code/claude-zh/skills/python-testing/SKILL.md

199 lines
4.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
name: python-testing
description: 使用 pytest 的 Python 測試策略,涵蓋 TDD 方法論、Fixtures、Mocking、參數化 (Parametrization) 以及覆蓋率要求。
---
# Python 測試模式 (Python Testing Patterns)
使用 pytest、TDD 方法論與最佳實踐的 Python 應用程式全面測試策略。
## 何時啟用
- 編寫新的 Python 程式碼(遵循 TDD紅燈、綠燈、重構
- 為 Python 專案設計測試套件。
- 審核 Python 測試覆蓋率。
- 設置測試基礎設施。
## 核心測試哲學
### 測試驅動開發 (TDD)
務必遵循 TDD 循環:
1. **紅燈 (RED)**:為預期行為撰寫一個會失敗的測試。
2. **綠燈 (GREEN)**:編寫最少量的程式碼使測試通過。
3. **重構 (REFACTOR)**:在保持測試通過的情況下優化程式碼。
```python
# 步驟 1撰寫失敗的測試 (RED)
def test_add_numbers():
result = add(2, 3)
assert result == 5
# 步驟 2撰寫最小實作 (GREEN)
def add(a, b):
return a + b
# 步驟 3如有需要則重構 (REFACTOR)
```
### 覆蓋率要求
- **目標**80% 以上的程式碼覆蓋率。
- **關鍵路徑**:必須達到 100% 覆蓋率。
- 使用 `pytest --cov` 來衡量覆蓋率。
```bash
pytest --cov=mypackage --cov-report=term-missing --cov-report=html
```
## pytest 基礎
### 斷言 (Assertions)
```python
# 相等性
assert result == expected
# 真值判斷
assert result # 真值 (Truthy)
assert result is True # 嚴格等於 True
assert result is None # 嚴格等於 None
# 成員資格
assert item in collection
# 類型檢查
assert isinstance(result, str)
# 例外測試
with pytest.raises(ValueError):
raise ValueError("錯誤訊息")
# 檢查例外訊息內容
with pytest.raises(ValueError, match="無效的輸入"):
raise ValueError("提供的輸入無效")
```
## Fixtures (測試夾具)
### 帶有設置與清理的 Fixture
```python
@pytest.fixture
def database():
"""帶有設置與清理 (Setup/Teardown) 的 Fixture。"""
# 設置 (Setup)
db = Database(":memory:")
db.create_tables()
yield db # 提供給測試案例使用
# 清理 (Teardown)
db.close()
def test_database_query(database):
"""測試資料庫操作。"""
result = database.query("SELECT * FROM users")
assert len(result) > 0
```
### 作用域 (Scopes) 與 conftest.py
- **function** (預設):每個測試執行一次。
- **module**:每個模組執行一次。
- **session**:每次測試會話執行一次。
- 使用 `tests/conftest.py` 來定義跨檔案共享的 Fixtures。
## 參數化 (Parametrization)
```python
@pytest.mark.parametrize("input,expected", [
("hello", "HELLO"),
("world", "WORLD"),
("PyThOn", "PYTHON"),
])
def test_uppercase(input, expected):
"""測試會執行 3 次,每次使用不同的輸入。"""
assert input.upper() == expected
```
## Mocking 與 Patching
```python
from unittest.mock import patch, Mock
@patch("mypackage.external_api_call")
def test_with_mock(api_call_mock):
"""測試模擬外部 API。"""
api_call_mock.return_value = {"status": "success"}
result = my_function()
api_call_mock.assert_called_once()
assert result["status"] == "success"
```
## 測試非同步程式碼 (Async Code)
需要 `pytest-asyncio` 外掛:
```python
import pytest
@pytest.mark.asyncio
async def test_async_function():
"""測試非同步函式。"""
result = await async_add(2, 3)
assert result == 5
```
## 測試組織與最佳實踐
### 目錄結構建議
```
tests/
├── conftest.py # 共享 Fixtures
├── unit/ # 單元測試
│ └── test_models.py
├── integration/ # 整合測試
│ └── test_api.py
└── e2e/ # 端到端測試
└── test_user_flow.py
```
### 應做事項 (DO)
- **遵循 TDD**:先寫測試再寫程式碼。
- **單一功能測試**:每個測試案例僅驗證一個行為。
- **命名具備描述性**:例如 `test_user_login_with_invalid_credentials_fails`
- **模擬外部依賴**:不要依賴外部服務或網路。
### 避免事項 (DON'T)
- **不要測試實作細節**:應測試行為而非內部邏輯。
- **不要在測試中使用複雜的判斷式**:保持測試簡單明瞭。
- **不要忽視失敗的測試**:所有測試必須全部通過。
- **不要在測試之間共享狀態**:測試案例應保持獨立。
## 常用指令
```bash
# 執行所有測試
pytest
# 執行特定檔案
pytest tests/test_utils.py
# 執行特定測試案例
pytest tests/test_utils.py::test_function
# 帶有詳細輸出
pytest -v
# 執行直到第一次失敗即停止
pytest -x
# 重新執行上次失敗的測試
pytest --lf
```