Python Testing

affaan-m/ECC/.kiro/skills/python-testing

作者 affaan-mef648e01899ba3e8dc6371642deaaf64b4477775無授權條款275K 個星標收錄於 2026年10月9日更新於 2026年10月9日儲存庫4 天前更新

Python testing best practices using pytest including fixtures, parametrization, mocking, coverage analysis, async testing, and test organization. Use when writing or improving Python tests.

AI 產生的概覽

提供使用 pytest 進行 Python 測試的實務指引,涵蓋 fixture、參數化、模擬、覆蓋率與非同步測試。

功能
此技能提供使用 pytest 撰寫 Python 測試的參考模式與慣例。內容涵蓋測試探索、fixture 及其作用域、參數化、標記、使用 unittest.mock 與 pytest-mock 進行模擬、覆蓋率報告、非同步測試、例外斷言、以 hypothesis 為基礎的屬性測試,以及測試目錄組織。它產出的是指引與程式碼範例,而非可執行工具。
適用情境
適用於撰寫新的 Python 測試、提升測試覆蓋率,或建置 pytest 基礎設施時。也適合偵錯不穩定的測試、加入整合測試,以及測試非同步 Python 程式碼。
執行需求
不含任何指令碼,內容僅為說明與程式碼範例。依範例操作需要已安裝 pytest 的 Python 環境,以及選用套件,例如 pytest-cov、pytest-mock、pytest-asyncio、pytest-xdist、hypothesis,以及資料庫 fixture 範例所用的 SQLAlchemy。

Python Testing

This skill provides comprehensive Python testing patterns using pytest as the primary testing framework.

Testing Framework

Use pytest as the testing framework for its powerful features and clean syntax.

Basic Test Structure

python
def test_user_creation():    """Test that a user can be created with valid data"""    user = User(name="Alice", email="[email protected]")
    assert user.name == "Alice"    assert user.email == "[email protected]"    assert user.is_active is True

Test Discovery

pytest automatically discovers tests following these conventions:

  • Files: test_*.py or *_test.py
  • Functions: test_*
  • Classes: Test* (without __init__)
  • Methods: test_*

Fixtures

Fixtures provide reusable test setup and teardown:

python
import pytestfrom sqlalchemy import create_enginefrom sqlalchemy.orm import sessionmaker
@pytest.fixturedef db_session():    """Provide a database session for tests"""    engine = create_engine("sqlite:///:memory:")    Session = sessionmaker(bind=engine)    session = Session()
    # Setup    Base.metadata.create_all(engine)
    yield session
    # Teardown    session.close()
def test_user_repository(db_session):    """Test using the db_session fixture"""    repo = UserRepository(db_session)    user = repo.create(name="Alice", email="[email protected]")
    assert user.id is not None

Fixture Scopes

python
@pytest.fixture(scope="function")  # Default: per testdef user():    return User(name="Alice")
@pytest.fixture(scope="class")  # Per test classdef database():    db = Database()    db.connect()    yield db    db.disconnect()
@pytest.fixture(scope="module")  # Per moduledef app():    return create_app()
@pytest.fixture(scope="session")  # Once per test sessiondef config():    return load_config()

Fixture Dependencies

python
@pytest.fixturedef database():    db = Database()    db.connect()    yield db    db.disconnect()
@pytest.fixturedef user_repository(database):    """Fixture that depends on database fixture"""    return UserRepository(database)
def test_create_user(user_repository):    user = user_repository.create(name="Alice")    assert user.id is not None

Parametrization

Test multiple inputs with @pytest.mark.parametrize:

python
import pytest
@pytest.mark.parametrize("email,expected", [    ("[email protected]", True),    ("invalid-email", False),    ("", False),    ("user@", False),    ("@example.com", False),])def test_email_validation(email, expected):    result = validate_email(email)    assert result == expected

Multiple Parameters

python
@pytest.mark.parametrize("name,age,valid", [    ("Alice", 25, True),    ("Bob", 17, False),    ("", 25, False),    ("Charlie", -1, False),])def test_user_validation(name, age, valid):    result = validate_user(name, age)    assert result == valid

Parametrize with IDs

python
@pytest.mark.parametrize("input,expected", [    ("hello", "HELLO"),    ("world", "WORLD"),], ids=["lowercase", "another_lowercase"])def test_uppercase(input, expected):    assert input.upper() == expected

Test Markers

Use markers for test categorization and selective execution:

python
import pytest
@pytest.mark.unitdef test_calculate_total():    """Fast unit test"""    assert calculate_total([1, 2, 3]) == 6
@pytest.mark.integrationdef test_database_connection():    """Slower integration test"""    db = Database()    assert db.connect() is True
@pytest.mark.slowdef test_large_dataset():    """Very slow test"""    process_million_records()
@pytest.mark.skip(reason="Not implemented yet")def test_future_feature():    pass
@pytest.mark.skipif(sys.version_info < (3, 10), reason="Requires Python 3.10+")def test_new_syntax():    pass

Run specific markers:

bash
pytest -m unit              # Run only unit testspytest -m "not slow"        # Skip slow testspytest -m "unit or integration"  # Run unit OR integration

Mocking

Using unittest.mock

python
from unittest.mock import Mock, patch, MagicMock
def test_user_service_with_mock():    """Test with mock repository"""    mock_repo = Mock()    mock_repo.find_by_id.return_value = User(id="1", name="Alice")
    service = UserService(mock_repo)    user = service.get_user("1")
    assert user.name == "Alice"    mock_repo.find_by_id.assert_called_once_with("1")
@patch('myapp.services.EmailService')def test_send_notification(mock_email_service):    """Test with patched dependency"""    service = NotificationService()    service.send("[email protected]", "Hello")
    mock_email_service.send.assert_called_once()

pytest-mock Plugin

python
def test_with_mocker(mocker):    """Using pytest-mock plugin"""    mock_repo = mocker.Mock()    mock_repo.find_by_id.return_value = User(id="1", name="Alice")
    service = UserService(mock_repo)    user = service.get_user("1")
    assert user.name == "Alice"

Coverage Analysis

Basic Coverage

bash
pytest --cov=src --cov-report=term-missing

HTML Coverage Report

bash
pytest --cov=src --cov-report=htmlopen htmlcov/index.html

Coverage Configuration

ini
# pytest.ini or pyproject.toml[tool.pytest.ini_options]addopts = """    --cov=src    --cov-report=term-missing    --cov-report=html    --cov-fail-under=80"""

Branch Coverage

bash
pytest --cov=src --cov-branch

Async Testing

Testing Async Functions

python
import pytest
@pytest.mark.asyncioasync def test_async_fetch_user():    """Test async function"""    user = await fetch_user("1")    assert user.name == "Alice"
@pytest.fixtureasync def async_client():    """Async fixture"""    client = AsyncClient()    await client.connect()    yield client    await client.disconnect()
@pytest.mark.asyncioasync def test_with_async_fixture(async_client):    result = await async_client.get("/users/1")    assert result.status == 200

Test Organization

Directory Structure

tests/├── unit/│   ├── test_models.py│   ├── test_services.py│   └── test_utils.py├── integration/│   ├── test_database.py│   └── test_api.py├── conftest.py          # Shared fixtures└── pytest.ini           # Configuration

conftest.py

python
# tests/conftest.pyimport pytest
@pytest.fixture(scope="session")def app():    """Application fixture available to all tests"""    return create_app()
@pytest.fixturedef client(app):    """Test client fixture"""    return app.test_client()
def pytest_configure(config):    """Register custom markers"""    config.addinivalue_line("markers", "unit: Unit tests")    config.addinivalue_line("markers", "integration: Integration tests")    config.addinivalue_line("markers", "slow: Slow tests")

Assertions

Basic Assertions

python
def test_assertions():    assert value == expected    assert value != other    assert value > 0    assert value in collection    assert isinstance(value, str)

pytest Assertions with Better Error Messages

python
def test_with_context():    """pytest provides detailed assertion introspection"""    result = calculate_total([1, 2, 3])    expected = 6
    # pytest shows: assert 5 == 6    assert result == expected

Custom Assertion Messages

python
def test_with_message():    result = process_data(input_data)    assert result.is_valid, f"Expected valid result, got errors: {result.errors}"

Approximate Comparisons

python
import pytest
def test_float_comparison():    result = 0.1 + 0.2    assert result == pytest.approx(0.3)
    # With tolerance    assert result == pytest.approx(0.3, abs=1e-9)

Exception Testing

python
import pytest
def test_raises_exception():    """Test that function raises expected exception"""    with pytest.raises(ValueError):        validate_age(-1)
def test_exception_message():    """Test exception message"""    with pytest.raises(ValueError, match="Age must be positive"):        validate_age(-1)
def test_exception_details():    """Capture and inspect exception"""    with pytest.raises(ValidationError) as exc_info:        validate_user(name="", age=-1)
    assert "name" in exc_info.value.errors    assert "age" in exc_info.value.errors

Test Helpers

python
# tests/helpers.pydef assert_user_equal(actual, expected):    """Custom assertion helper"""    assert actual.id == expected.id    assert actual.name == expected.name    assert actual.email == expected.email
def create_test_user(**kwargs):    """Test data factory"""    defaults = {        "name": "Test User",        "email": "[email protected]",        "age": 25,    }    defaults.update(kwargs)    return User(**defaults)

Property-Based Testing

Using hypothesis for property-based testing:

python
from hypothesis import given, strategies as st
@given(st.integers(), st.integers())def test_addition_commutative(a, b):    """Test that addition is commutative"""    assert a + b == b + a
@given(st.lists(st.integers()))def test_sort_idempotent(lst):    """Test that sorting twice gives same result"""    sorted_once = sorted(lst)    sorted_twice = sorted(sorted_once)    assert sorted_once == sorted_twice

Best Practices

  1. One assertion per test (when possible)
  2. Use descriptive test names - describe what's being tested
  3. Arrange-Act-Assert pattern - clear test structure
  4. Use fixtures for setup - avoid duplication
  5. Mock external dependencies - keep tests fast and isolated
  6. Test edge cases - empty inputs, None, boundaries
  7. Use parametrize - test multiple scenarios efficiently
  8. Keep tests independent - no shared state between tests

Running Tests

bash
# Run all testspytest
# Run specific filepytest tests/test_user.py
# Run specific testpytest tests/test_user.py::test_create_user
# Run with verbose outputpytest -v
# Run with output capture disabledpytest -s
# Run in parallel (requires pytest-xdist)pytest -n auto
# Run only failed tests from last runpytest --lf
# Run failed tests firstpytest --ff

When to Use This Skill

  • Writing new Python tests
  • Improving test coverage
  • Setting up pytest infrastructure
  • Debugging flaky tests
  • Implementing integration tests
  • Testing async Python code

來源與署名

來源:affaan-m/ECC位於.kiro/skills/python-testing提交ef648e0

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架