跳到正文
cccskills
免费在 GitHub 公开

pytest

Use when writing Python tests with pytest - fixtures, parametrization, markers, conftest layout, pytest-asyncio, pytest-django, coverage with pytest-cov, mocking with pytest-mock, parallel runs with xdist, mutation testing with mutmut, or CI integration

中文简介正在准备中。当前显示原文说明。

查看安装方法

包含的文件(6)

  • SKILL.md14.1 KB
  • references/mocking-ci.md3.8 KB
  • references/mutation-testing.md10.5 KB
  • references/plugins-async-django.md9.3 KB
  • references/plugins-tooling.md7.9 KB
  • references/property-based-testing.md6.9 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

pytest

Complete reference for Python testing with pytest.

Overview

pytest is a mature, full-featured Python testing framework that makes it easy to write simple and scalable tests.

Key Features:

  • Simple: Write tests with plain assert statements
  • Powerful: Fixtures for setup/teardown
  • Parametrized: Run same test with different inputs
  • Plugins: Rich ecosystem of plugins
  • Parallel: Run tests in parallel with pytest-xdist
  • Detailed: Informative failure messages

Installation

pip install pytest>=9.0
pip install pytest-cov>=5.0      # Coverage
pip install pytest-mock     # Mocking
pip install pytest-asyncio>=0.25 # Async tests
pip install pytest-django>=4.8   # Django testing
pip install pytest-xdist    # Parallel execution

Python Version Requirements

pytest 9.0 requires Python 3.10+. Python 3.9 is no longer supported.

Breaking Changes in pytest 9.0

Errors from previously deprecated behavior:

  • py.path.local usage in hooks → Use pathlib.Path instead
  • Mixing async + sync fixtures → Now raises errors
  • @pytest.mark.usefixtures on fixture functions → Now errors
  • pytest.importorskip with __import__ → Removed

CI Mode: pytest enforces stricter configuration validation in CI environments.

Test Discovery

Naming Conventions

# Files must match pattern
test_*.py
*_test.py

# Classes must start with Test
class TestClass:
    def test_method(self):
        pass

# Functions must start with test_
def test_function():
    pass
# pytest.ini - Custom discovery patterns
[pytest]
python_files = test_*.py check_*.py
python_classes = Test* Check*
python_functions = test_* check_*

Fixtures

Basic Fixtures

# conftest.py or test file
import pytest

@pytest.fixture
def sample_data():
    """Provide sample data for tests."""
    return {"name": "test", "value": 42}

def test_with_fixture(sample_data):
    assert sample_data["name"] == "test"
    assert sample_data["value"] == 42

Fixture Scopes

@pytest.fixture(scope="function")
def function_fixture():
    """Created for each test (default)."""
    print("Setup: function scope")
    yield {"data": "function"}
    print("Teardown: function scope")

@pytest.fixture(scope="class")
def class_fixture():
    """Created once per test class."""
    print("Setup: class scope")
    yield {"data": "class"}
    print("Teardown: class scope")

@pytest.fixture(scope="module")
def module_fixture():
    """Created once per module."""
    print("Setup: module scope")
    yield {"data": "module"}
    print("Teardown: module scope")

@pytest.fixture(scope="package")
def package_fixture():
    """Created once per package."""
    print("Setup: package scope")
    yield {"data": "package"}
    print("Teardown: package scope")

@pytest.fixture(scope="session")
def session_fixture():
    """Created once per test session."""
    print("Setup: session scope")
    yield {"data": "session"}
    print("Teardown: session scope")

Yield Fixtures (Setup/Teardown)

@pytest.fixture
def database():
    """Setup and teardown database."""
    db = Database(':memory:')
    db.create_tables()
    yield db
    db.close()

@pytest.fixture
def temp_file():
    """Create temporary file for testing."""
    import tempfile
    import os
    
    fd, path = tempfile.mkstemp()
    os.close(fd)
    
    yield path
    
    # Cleanup
    if os.path.exists(path):
        os.unlink(path)

def test_database(database):
    database.insert({"name": "test"})
    assert database.count() == 1

autouse Fixtures

@pytest.fixture(autouse=True)
def setup_test_environment():
    """Automatically run for every test."""
    os.environ['TESTING'] = 'true'
    yield
    del os.environ['TESTING']

Fixture Factories

@pytest.fixture
def make_user():
    """Factory fixture for creating users."""
    created_users = []
    
    def _make_user(name, email, **kwargs):
        user = User.objects.create_user(
            username=name,
            email=email,
            **kwargs
        )
        created_users.append(user)
        return user
    
    yield _make_user
    
    # Cleanup all created users
    for user in created_users:
        user.delete()

def test_user_creation(make_user):
    user1 = make_user("user1", "user1@example.com")
    user2 = make_user("user2", "user2@example.com", is_staff=True)
    
    assert user1.username == "user1"
    assert user2.is_staff is True

conftest.py

# conftest.py - Shared fixtures for all tests in directory

import pytest
from myapp import create_app, db

@pytest.fixture(scope="session")
def app():
    """Create application for testing."""
    app = create_app(config="testing")
    yield app

@pytest.fixture(scope="function")
def client(app):
    """Create test client."""
    return app.test_client()

@pytest.fixture(scope="function")
def db_session(app):
    """Create database session."""
    with app.app_context():
        db.create_all()
        yield db
        db.session.remove()
        db.drop_all()

Parametrization

Basic Parametrization

import pytest

@pytest.mark.parametrize("input,expected", [
    (1, 2),
    (2, 4),
    (3, 6),
    (10, 20),
])
def test_double(input, expected):
    assert input * 2 == expected

@pytest.mark.parametrize("value", [
    1,
    1.5,
    "string",
    [1, 2, 3],
    {"key": "value"},
])
def test_json_serializable(value):
    import json
    assert json.dumps(value) is not None

Multiple Parameters

@pytest.mark.parametrize("x,y,expected", [
    (1, 2, 3),
    (5, 5, 10),
    (0, 0, 0),
    (-1, 1, 0),
])
def test_add(x, y, expected):
    assert x + y == expected

Parametrize with IDs

@pytest.mark.parametrize("input,expected", [
    ("hello", "HELLO"),
    ("WORLD", "WORLD"),
    ("MixEd", "MIXED"),
], ids=["lowercase", "uppercase", "mixed"])
def test_uppercase(input, expected):
    assert input.upper() == expected

# Custom ID function
def idfn(val):
    if isinstance(val, str):
        return f"str_{val[:5]}"
    return str(val)

@pytest.mark.parametrize("value", ["hello", "world", "test"], ids=idfn)
def test_with_custom_ids(value):
    assert len(value) > 0

Parametrize with Fixtures

@pytest.fixture(params=[
    ("admin", True),
    ("user", False),
    ("guest", False),
])
def user_with_role(request):
    role, is_admin = request.param
    return {"role": role, "is_admin": is_admin}

def test_user_role(user_with_role):
    role = user_with_role["role"]
    is_admin = user_with_role["is_admin"]
    
    if role == "admin":
        assert is_admin is True
    else:
        assert is_admin is False

Indirect Parametrization

@pytest.fixture
def user(request):
    """Create user based on parameter."""
    role = request.param
    return User.objects.create_user(username=f"test_{role}", role=role)

@pytest.mark.parametrize("user", ["admin", "user", "guest"], indirect=True)
def test_user_access(user):
    """Test access based on user role."""
    if user.role == "admin":
        assert user.can_access_admin()
    else:
        assert not user.can_access_admin()

Markers

Built-in Markers

import pytest

# Skip test
@pytest.mark.skip(reason="Not implemented yet")
def test_future_feature():
    pass

# Skip conditionally
@pytest.mark.skipif(sys.version_info < (3, 10), reason="Requires Python 3.10+")
def test_python_310_feature():
    pass

# Expected to fail
@pytest.mark.xfail(reason="Known bug #123")
def test_known_bug():
    assert 1 == 2

# Expected to fail conditionally
@pytest.mark.xfail(condition=sys.platform == "win32", reason="Windows issue")
def test_platform_specific():
    pass

# Expected failure but run anyway
@pytest.mark.xfail(strict=False)
def test_might_pass():
    pass

Custom Markers

# Register marker in pytest.ini
[pytest]
markers =
    slow: marks tests as slow (deselect with '-m "not slow"')
    integration: marks tests as integration tests
    unit: marks tests as unit tests
    requires_db: marks tests that need database

# Use custom markers
@pytest.mark.slow
def test_slow_operation():
    time.sleep(10)
    assert True

@pytest.mark.integration
def test_external_api():
    response = requests.get("https://api.example.com")
    assert response.status_code == 200

# Multiple markers
@pytest.mark.slow
@pytest.mark.integration
def test_slow_integration():
    pass

# Run tests with markers
# pytest -m slow          # Run only slow tests
# pytest -m "not slow"    # Skip slow tests
# pytest -m "slow and integration"

Configuration

pytest.ini

[pytest]
# Test discovery
python_files = test_*.py *_test.py
python_classes = Test*
python_functions = test_*

# Command line options
addopts = -v --tb=short --cov=myapp

# Markers
markers =
    slow: slow tests
    integration: integration tests
    unit: unit tests

# Minimum pytest version
minversion = 9.0

# Required plugins
required_plugins = pytest-cov>=5.0 pytest-mock pytest-asyncio>=0.25 pytest-django>=4.8

# Logging
log_cli = true
log_cli_level = INFO

# Timeout
timeout = 300
timeout_method = thread

# Coverage
testpaths = tests

pyproject.toml

[tool.pytest.ini_options]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = "-v --tb=short"
testpaths = ["tests"]
markers = [
    "slow: marks tests as slow",
    "integration: marks tests as integration tests",
]

conftest.py Structure

# tests/conftest.py - Root conftest
import pytest

# Command line options
def pytest_addoption(parser):
    parser.addoption(
        "--run-slow",
        action="store_true",
        default=False,
        help="run slow tests"
    )

# Skip slow tests by default
def pytest_collection_modifyitems(config, items):
    if config.getoption("--run-slow"):
        return
    
    skip_slow = pytest.mark.skip(reason="need --run-slow option")
    for item in items:
        if "slow" in item.keywords:
            item.add_marker(skip_slow)

# Custom fixtures available to all tests
@pytest.fixture(scope="session")
def test_config():
    return {"debug": True}

Deep Dives

For detailed coverage of these topics, load the corresponding reference files:

  • Async Testing & Django — references/plugins-async-django.md: pytest-asyncio, pytest-django, async patterns, sync_to_async bridge
  • Tooling Plugins — references/plugins-tooling.md: pytest-cov (coverage), pytest-mock, pytest-xdist, pytest-timeout
  • Mocking & CI — references/mocking-ci.md: unittest.mock patterns, monkeypatch, CI/CD integration
  • Mutation Testing — references/mutation-testing.md: mutmut workflow, cargo-mutants, Stryker, when tests don't assert anything, fork hazards — load when verifying assertion quality beyond coverage
  • Property-Based Testing — references/property-based-testing.md: Hypothesis @given + strategies, deadline/max_examples/profiles, shrinking-to-@example workflow — load when testing parsers, roundtrips, or invariants over generated inputs

Best Practices

1. Test Organization

tests/
├── conftest.py           # Shared fixtures
├── unit/                 # Unit tests
│   ├── __init__.py
│   ├── test_models.py
│   └── test_utils.py
├── integration/          # Integration tests
│   ├── __init__.py
│   └── test_api.py
├── e2e/                  # End-to-end tests
│   ├── __init__.py
│   └── test_flows.py
└── fixtures/             # Test data
    └── data.json

2. Clear Test Names

# Bad
def test_user():
    pass

# Good
def test_create_user_with_valid_data_succeeds():
    pass

def test_create_user_with_duplicate_email_raises_error():
    pass

def test_user_cannot_delete_own_account():
    pass

3. AAA Pattern

def test_user_creation():
    # Arrange
    user_data = {
        "username": "testuser",
        "email": "test@example.com",
        "password": "password123"
    }
    
    # Act
    user = User.create(**user_data)
    
    # Assert
    assert user.username == "testuser"
    assert user.email == "test@example.com"
    assert user.check_password("password123")

4. One Behavior Per Test

Each test verifies one behavior — that is not "one assertion": multiple assertions on the SAME behavior are fine and often clearer than splitting.

# Good — all assertions verify one behavior: create_user persisted each field
def test_create_user_persists_fields():
    user = create_user(username="test", email="test@example.com")
    assert user.username == "test"
    assert user.email == "test@example.com"

# Bad — two different behaviors in one test; a failure doesn't say which broke
def test_user_and_password():
    user = create_user()
    assert user.username == "test"
    assert user.check_password("password123")

Split into separate tests when behaviors differ. The real driver is setup cost: duplicating expensive setup across near-identical tests is worse than a second assertion on the same behavior — share setup with fixtures instead of merging unrelated behaviors into one test.

Mutation Testing

Coverage tells you which lines ran; mutation testing tells you if your tests would notice if those lines were wrong. Run mutmut run to mutate code and verify tests catch the changes, then mutmut browse to review survivors. A high-coverage suite with weak assertions scores badly here — see references/mutation-testing.md for the full workflow.

References

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

aiohttp

免费

Use when building Python async HTTP services or clients with aiohttp - web server routing, middleware, WebSocket, SSE, streaming, client sessions, pytest-aiohttp testing, or troubleshooting SSL and timeout issues

中文简介正在准备中。当前显示原文说明。

CodeAtCode/oss-ai-skills242026年10月9日 更新

ast-grep

免费

Use when doing structural code search and rewriting - ast-grep linting, refactoring, multi-language patterns

中文简介正在准备中。当前显示原文说明。

CodeAtCode/oss-ai-skills242026年10月9日 更新

Use when building GBA games with the BPCore Lua engine - entity, sprite and tilemap functions, SRAM save and load, link cable multiplayer protocol, camera and scrolling, or optimization patterns

中文简介正在准备中。当前显示原文说明。

CodeAtCode/oss-ai-skills242026年10月9日 更新

celery

免费

Use when running background tasks with Celery - worker and broker configuration (Redis, RabbitMQ), task routing by name vs queue, chains/groups/chords, retry patterns (autoretry_for, retry_backoff), acks_late semantics, failure detection, and monitoring with Flower

中文简介正在准备中。当前显示原文说明。

CodeAtCode/oss-ai-skills242026年10月9日 更新

django

免费

Use when building Django applications - security hardening, authentication and permissions, ORM optimization, PostgreSQL features, Django 6.0, migrations, testing, and ecosystem libraries

中文简介正在准备中。当前显示原文说明。

CodeAtCode/oss-ai-skills242026年10月9日 更新

Use when customizing Django Admin - save_formset, get_search_results, formsets, queryset optimization, db_index, custom URLs

中文简介正在准备中。当前显示原文说明。

CodeAtCode/oss-ai-skills242026年10月9日 更新

查看 CodeAtCode 的全部技能

报告此技能的问题