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 测试的实践指导,涵盖夹具、参数化、模拟、覆盖率和异步测试。

功能
该技能提供使用 pytest 编写 Python 测试的参考模式与约定。内容涵盖测试发现、夹具及其作用域、参数化、标记、使用 unittest.mock 与 pytest-mock 进行模拟、覆盖率报告、异步测试、异常断言、基于 hypothesis 的属性测试,以及测试目录组织。它产出的是指导说明和代码示例,而非可执行工具。
适用场景
适用于编写新的 Python 测试、提升测试覆盖率或搭建 pytest 基础设施时。也适合调试不稳定的测试、添加集成测试以及测试异步 Python 代码。
运行要求
不包含脚本,内容仅为说明和代码示例。按示例操作需要已安装 pytest 的 Python 环境,以及可选包,如 pytest-cov、pytest-mock、pytest-asyncio、pytest-xdist、hypothesis,以及数据库夹具示例所用的 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 从公开仓库中收录这些内容。

举报或申请下架