Communitygithub.com

python-testing

Python 測試策略,使用 pytest、TDD 方法論、fixtures、mock、參數化與覆蓋率要求。適用於撰寫 pytest 測試(fixtures、mock、參數化或覆蓋率)。

python-testing 是什麼?

此技能提供一套完整的 Python 測試指南,專注於 pytest 框架、測試驅動開發(TDD)方法論,以及涵蓋 fixtures、mock、參數化與覆蓋率要求等最佳實務。它會指導使用者遵循 TDD 的「紅-綠-重構」循環,設定 80% 以上的程式碼覆蓋率目標,並利用 pytest 的 fixtures 進行設定與拆除作業、使用 mock 測試外部相依性,以及透過參數化簡化多個測試案例。此技能支援各種 Python 專案,並涵蓋進階主題,例如非同步測試、例外處理,以及使用 tmp_path 或 pytest 內建 fixtures 測試副作用。開發者可以參考它來建立穩固的測試套件、改善測試組織結構,並透過 pytest 標記和選擇性執行來提升測試效率。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/python-testing

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

Python 測試模式

使用 pytest、TDD 方法論及最佳實務的綜合性 Python 應用程式測試策略。

啟用時機

  • 撰寫新的 Python 程式碼(遵循 TDD:紅、綠、重構)
  • 為 Python 專案設計測試套件
  • 檢視 Python 測試覆蓋率
  • 設定測試基礎架構

核心測試哲學

測試驅動開發 (TDD)

始終遵循 TDD 循環:

  1. 紅:為期望的行為撰寫一個會失敗的測試
  2. 綠:撰寫最少的程式碼讓測試通過
  3. 重構:在保持測試通過的同時改善程式碼
# 步驟 1:撰寫會失敗的測試 (紅)
def test_add_numbers():
    result = add(2, 3)
    assert result == 5

# 步驟 2:撰寫最小的實作 (綠)
def add(a, b):
    return a + b

# 步驟 3:如有需要進行重構 (重構)

覆蓋率要求

  • 目標:80% 以上的程式碼覆蓋率
  • 關鍵路徑:需要 100% 覆蓋率
  • 使用 pytest --cov 來衡量覆蓋率
pytest --cov=mypackage --cov-report=term-missing --cov-report=html

pytest 基礎

基本測試結構

import pytest

def test_addition():
    """測試基本加法。"""
    assert 2 + 2 == 4

def test_string_uppercase():
    """測試字串轉大寫。"""
    text = "hello"
    assert text.upper() == "HELLO"

def test_list_append():
    """測試列表附加元素。"""
    items = [1, 2, 3]
    items.append(4)
    assert 4 in items
    assert len(items) == 4

斷言

# 相等
assert result == expected

# 不相等
assert result != unexpected

# 真值
assert result  # 為真
assert not result  # 為假
assert result is True  # 確認為 True
assert result is False  # 確認為 False
assert result is None  # 確認為 None

# 成員
assert item in collection
assert item not in collection

# 比較
assert result > 0
assert 0 <= result <= 100

# 型別檢查
assert isinstance(result, str)

# 例外測試(偏好方法)
with pytest.raises(ValueError):
    raise ValueError("錯誤訊息")

# 檢查例外訊息
with pytest.raises(ValueError, match="invalid input"):
    raise ValueError("invalid input provided")

# 檢查例外屬性
with pytest.raises(ValueError) as exc_info:
    raise ValueError("錯誤訊息")
assert str(exc_info.value) == "錯誤訊息"

Fixtures

基本 Fixture 用法

import pytest

@pytest.fixture
def sample_data():
    """提供範例資料的 fixture。"""
    return {"name": "Alice", "age": 30}

def test_sample_data(sample_data):
    """使用 fixture 的測試。"""
    assert sample_data["name"] == "Alice"
    assert sample_data["age"] == 30

帶有設定/拆除的 Fixture

@pytest.fixture
def database():
    """帶有設定和拆除的 fixture。"""
    # 設定
    db = Database(":memory:")
    db.create_tables()
    db.insert_test_data()

    yield db  # 提供給測試

    # 拆除
    db.close()

def test_database_query(database):
    """測試資料庫操作。"""
    result = database.query("SELECT * FROM users")
    assert len(result) > 0

Fixture 作用域

# 函式作用域 (預設) - 每個測試執行一次
@pytest.fixture
def temp_file():
    with open("temp.txt", "w") as f:
        yield f
    os.remove("temp.txt")

# 模組作用域 - 每個模組執行一次
@pytest.fixture(scope="module")
def module_db():
    db = Database(":memory:")
    db.create_tables()
    yield db
    db.close()

# 會話作用域 - 每次測試會話執行一次
@pytest.fixture(scope="session")
def shared_resource():
    resource = ExpensiveResource()
    yield resource
    resource.cleanup()

帶參數的 Fixture

@pytest.fixture(params=[1, 2, 3])
def number(request):
    """參數化的 fixture。"""
    return request.param

def test_numbers(number):
    """測試會執行 3 次,每次使用一個參數。"""
    assert number > 0

使用多個 Fixtures

@pytest.fixture
def user():
    return User(id=1, name="Alice")

@pytest.fixture
def admin():
    return User(id=2, name="Admin", role="admin")

def test_user_admin_interaction(user, admin):
    """測試使用多個 fixtures。"""
    assert admin.can_manage(user)

自動使用 Fixtures

@pytest.fixture(autouse=True)
def reset_config():
    """自動在每個測試之前執行。"""
    Config.reset()
    yield
    Config.cleanup()

def test_without_fixture_call():
    # reset_config 會自動執行
    assert Config.get_setting("debug") is False

用於共用 Fixtures 的 Conftest.py

# tests/conftest.py
import pytest

@pytest.fixture
def client():
    """所有測試共用的 fixture。"""
    app = create_app(testing=True)
    with app.test_client() as client:
        yield client

@pytest.fixture
def auth_headers(client):
    """為 API 測試產生驗證標頭。"""
    response = client.post("/api/login", json={
        "username": "test",
        "password": "test"
    })
    token = response.json["token"]
    return {"Authorization": f"Bearer {token}"}

參數化

基本參數化

@pytest.mark.parametrize("input,expected", [
    ("hello", "HELLO"),
    ("world", "WORLD"),
    ("PyThOn", "PYTHON"),
])
def test_uppercase(input, expected):
    """測試會執行 3 次,每次使用不同的輸入。"""
    assert input.upper() == expected

多個參數

@pytest.mark.parametrize("a,b,expected", [
    (2, 3, 5),
    (0, 0, 0),
    (-1, 1, 0),
    (100, 200, 300),
])
def test_add(a, b, expected):
    """測試加法與多個輸入。"""
    assert add(a, b) == expected

帶 ID 的參數化

@pytest.mark.parametrize("input,expected", [
    ("[email protected]", True),
    ("invalid", False),
    ("@no-domain.com", False),
], ids=["valid-email", "missing-at", "missing-domain"])
def test_email_validation(input, expected):
    """測試電子郵件驗證與可讀的測試 ID。"""
    assert is_valid_email(input) is expected

參數化的 Fixtures

@pytest.fixture(params=["sqlite", "postgresql", "mysql"])
def db(request):
    """針對多個資料庫後端進行測試。"""
    if request.param == "sqlite":
        return Database(":memory:")
    elif request.param == "postgresql":
        return Database("postgresql://localhost/test")
    elif request.param == "mysql":
        return Database("mysql://localhost/test")

def test_database_operations(db):
    """測試會執行 3 次,每個資料庫一次。"""
    result = db.query("SELECT 1")
    assert result is not None

標記與測試選擇

自訂標記

# 標記慢速測試
@pytest.mark.slow
def test_slow_operation():
    time.sleep(5)

# 標記整合測試
@pytest.mark.integration
def test_api_integration():
    response = requests.get("https://api.example.com")
    assert response.status_code == 200

# 標記單元測試
@pytest.mark.unit
def test_unit_logic():
    assert calculate(2, 3) == 5

執行特定測試

# 只執行快速測試
pytest -m "not slow"

# 只執行整合測試
pytest -m integration

# 執行整合或慢速測試
pytest -m "integration or slow"

# 執行標記為 unit 但不是 slow 的測試
pytest -m "unit and not slow"

在 pytest.ini 中設定標記

[pytest]
markers =
    slow: 將測試標記為慢速
    integration: 將測試標記為整合測試
    unit: 將測試標記為單元測試
    django: 將測試標記為需要 Django

Mock 與 Patching

Mock 函式

from unittest.mock import patch, Mock

@patch("mypackage.external_api_call")
def test_with_mock(api_call_mock):
    """使用 mock 的外部 API 進行測試。"""
    api_call_mock.return_value = {"status": "success"}

    result = my_function()

    api_call_mock.assert_called_once()
    assert result["status"] == "success"

Mock 回傳值

@patch("mypackage.Database.connect")
def test_database_connection(connect_mock):
    """使用 mock 的資料庫連線進行測試。"""
    connect_mock.return_value = MockConnection()

    db = Database()
    db.connect()

    connect_mock.assert_called_once_with("localhost")

Mock 例外

@patch("mypackage.api_call")
def test_api_error_handling(api_call_mock):
    """使用 mock 的例外測試錯誤處理。"""
    api_call_mock.side_effect = ConnectionError("網路錯誤")

    with pytest.raises(ConnectionError):
        api_call()

    api_call_mock.assert_called_once()

Mock 內容管理器

@patch("builtins.open", new_callable=mock_open)
def test_file_reading(mock_file):
    """使用 mock 的 open 測試檔案讀取。"""
    mock_file.return_value.read.return_value = "file content"

    result = read_file("test.txt")

    mock_file.assert_called_once_with("test.txt", "r")
    assert result == "file content"

使用 Autospec

@patch("mypackage.DBConnection", autospec=True)
def test_autospec(db_mock):
    """使用 autospec 進行測試以捕捉 API 誤用。"""
    db = db_mock.return_value
    db.query("SELECT * FROM users")

    # 如果 DBConnection 沒有 query 方法,這將會失敗
    db_mock.assert_called_once()

Mock 類別實例

class TestUserService:
    @patch("mypackage.UserRepository")
    def test_create_user(self, repo_mock):
        """使用 mock 的儲存庫測試使用者建立。"""
        repo_mock.return_value.save.return_value = User(id=1, name="Alice")

        service = UserService(repo_mock.return_value)
        user = service.create_user(name="Alice")

        assert user.name == "Alice"
        repo_mock.return_value.save.assert_called_once()

Mock 屬性

@pytest.fixture
def mock_config():
    """建立一個帶有屬性的 mock。"""
    config = Mock()
    type(config).debug = PropertyMock(return_value=True)
    type(config).api_key = PropertyMock(return_value="test-key")
    return config

def test_with_mock_config(mock_config):
    """使用 mock 的設定屬性進行測試。"""
    assert mock_config.debug is True
    assert mock_config.api_key == "test-key"

測試非同步程式碼

使用 pytest-asyncio 的非同步測試

import pytest

@pytest.mark.asyncio
async def test_async_function():
    """測試非同步函式。"""
    result = await async_add(2, 3)
    assert result == 5

@pytest.mark.asyncio
async def test_async_with_fixture(async_client):
    """使用非同步 fixture 測試非同步功能。"""
    response = await async_client.get("/api/users")
    assert response.status_code == 200

非同步 Fixture

@pytest.fixture
async def async_client():
    """提供非同步測試用戶端的非同步 fixture。"""
    app = create_app()
    async with app.test_client() as client:
        yield client

@pytest.mark.asyncio
async def test_api_endpoint(async_client):
    """使用非同步 fixture 進行測試。"""
    response = await async_client.get("/api/data")
    assert response.status_code == 200

Mock 非同步函式

@pytest.mark.asyncio
@patch("mypackage.async_api_call")
async def test_async_mock(api_call_mock):
    """使用 mock 測試非同步函式。"""
    api_call_mock.return_value = {"status": "ok"}

    result = await my_async_function()

    api_call_mock.assert_awaited_once()
    assert result["status"] == "ok"

測試例外

測試預期的例外

def test_divide_by_zero():
    """測試除以零是否引發 ZeroDivisionError。"""
    with pytest.raises(ZeroDivisionError):
        divide(10, 0)

def test_custom_exception():
    """測試自訂例外與訊息。"""
    with pytest.raises(ValueError, match="invalid input"):
        validate_input("invalid")

測試例外屬性

def test_exception_with_details():
    """測試帶有自訂屬性的例外。"""
    with pytest.raises(CustomError) as exc_info:
        raise CustomError("錯誤", code=400)

    assert exc_info.value.code == 400
    assert "錯誤" in str(exc_info.value)

測試副作用

測試檔案操作

import tempfile
import os

def test_file_processing():
    """使用暫存檔案測試檔案處理。"""
    with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:
        f.write("test content")
        temp_path = f.name

    try:
        result = process_file(temp_path)
        assert result == "processed: test content"
    finally:
        os.unlink(temp_path)

使用 pytest 的 tmp_path Fixture 進行測試

def test_with_tmp_path(tmp_path):
    """使用 pytest 內建的暫存路徑 fixture 進行測試。"""
    test_file = tmp_path / "test.txt"
    test_file.write_text("hello world")

    result = process_file(str(test_file))
    assert result == "hello world"
    # tmp_path 會自動清理

使用 tmpdir Fixture 進行測試

def test_with_tmpdir(tmpdir):
    """使用 pytest 的 tmpdir fixture 進行測試。"""
    test_file = tmpdir.join("test.txt")
    test_file.write("data")

    result = process_file(str(test_file))
    assert result == "data"

測試組織

目錄結構

tests/
├── conftest.py                 # 共用的 fixtures
├── __init__.py
├── unit/                       # 單元測試
│   ├── __init__.py
│   ├── test_models.py
│   ├── test_utils.py
│   └── test_services.py
├── integration/                # 整合測試
│   ├── __init__.py
│   ├── test_api.py
│   └── test_database.py
└── e2e/                        # 端到端測試
    ├── __init__.py
    └── test_user_flow.py

測試類別

class TestUserService:
    """在類別中對相關測試進行分組。"""

    @pytest.fixture(autouse=True)
    def setup(self):
        """在此類別的每個測試之前執行設定。"""
        self.service = UserService()

    def test_create_user(self):
        """測試使用者建立。"""
        user = self.service.create_user("Alice")
        assert user.name == "Alice"

    def test_delete_u

...

[為翻譯而截斷]

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

相關技能