AgentSkillsCN

pytest

使用pytest框架进行Python测试,涵盖单元测试、集成测试和API测试。 当:(1) 编写Python代码的测试用例,(2) 设置pytest fixture, (3) 使用pytest-asyncio测试异步函数,(4) 模拟依赖项, (5) 参数化测试,(6) 测试FastAPI/Flask端点,(7) 设置测试覆盖率, (8) 使用factory_boy创建测试工厂,(9) 配置CI/CD测试流水线。

SKILL.md
--- frontmatter
name: pytest
description: |
  Python testing with pytest framework for unit, integration, and API tests.
  Use when: (1) Writing test cases for Python code, (2) Setting up pytest fixtures,
  (3) Testing async functions with pytest-asyncio, (4) Mocking dependencies,
  (5) Parameterizing tests, (6) Testing FastAPI/Flask endpoints, (7) Setting up test coverage,
  (8) Creating test factories with factory_boy, (9) Configuring CI/CD test pipelines.

Pytest Testing Skill

Comprehensive testing patterns for Python applications using pytest.

Quick Reference

FeatureReference File
Fixtures, conftest, scopesreferences/fixtures.md
Async testing, pytest-asyncioreferences/async-testing.md
Mocking, patching, spiesreferences/mocking.md
FastAPI/Flask endpoint testingreferences/api-testing.md

Dependencies

toml
[project.optional-dependencies]
dev = [
    "pytest>=8.0.0",
    "pytest-asyncio>=0.24.0",
    "pytest-cov>=4.1.0",
    "httpx>=0.28.0",           # Async HTTP client for API tests
    "factory-boy>=3.3.0",      # Test factories
    "faker>=33.0.0",           # Fake data generation
]

Configuration

pyproject.toml

toml
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
python_files = ["test_*.py"]
python_functions = ["test_*"]
addopts = "-v --tb=short"
filterwarnings = ["ignore::DeprecationWarning"]

[tool.coverage.run]
source = ["app"]
omit = ["*/tests/*", "*/__init__.py"]

Basic Test Structure

python
import pytest

# Simple test function
def test_addition():
    assert 1 + 1 == 2

# Test class (group related tests)
class TestCalculator:
    def test_add(self):
        assert add(2, 3) == 5

    def test_subtract(self):
        assert subtract(5, 3) == 2

# Expected exceptions
def test_division_by_zero():
    with pytest.raises(ZeroDivisionError):
        divide(1, 0)

# Parametrized tests
@pytest.mark.parametrize("input,expected", [
    (1, 1),
    (2, 4),
    (3, 9),
])
def test_square(input, expected):
    assert square(input) == expected

Fixtures

python
import pytest

@pytest.fixture
def sample_user():
    return {"name": "John", "email": "john@example.com"}

@pytest.fixture
def db_connection():
    conn = create_connection()
    yield conn       # Test runs here
    conn.close()     # Cleanup after test

# Use fixture in test
def test_user_name(sample_user):
    assert sample_user["name"] == "John"

Async Testing

python
import pytest

@pytest.mark.asyncio
async def test_async_function():
    result = await async_operation()
    assert result == "success"

@pytest.fixture
async def async_client():
    async with AsyncClient() as client:
        yield client

Mocking

python
from unittest.mock import patch, MagicMock, AsyncMock

def test_with_mock():
    with patch("module.external_api") as mock_api:
        mock_api.return_value = {"data": "mocked"}
        result = function_using_api()
        assert result["data"] == "mocked"
        mock_api.assert_called_once()

# Async mock
@pytest.mark.asyncio
async def test_async_mock():
    with patch("module.async_call", new_callable=AsyncMock) as mock:
        mock.return_value = "result"
        result = await function_with_async_call()
        assert result == "result"

Running Tests

bash
# Run all tests
pytest

# Verbose output
pytest -v

# Run specific file
pytest tests/test_users.py

# Run specific test
pytest tests/test_users.py::test_create_user

# Run with coverage
pytest --cov=app --cov-report=term-missing

# Stop on first failure
pytest -x

# Run last failed tests
pytest --lf

# Run tests matching pattern
pytest -k "user and not delete"

Project Structure

code
project/
├── app/
│   ├── __init__.py
│   ├── main.py
│   └── services/
├── tests/
│   ├── __init__.py
│   ├── conftest.py      # Shared fixtures
│   ├── test_main.py
│   └── test_services/
└── pyproject.toml