Claude
Skills
Sign in
Back

python-testing

Included with Lifetime
$97 forever

Complete Python testing system. PROACTIVELY activate for: (1) pytest fundamentals, (2) Fixtures and scopes, (3) Parameterized tests, (4) Mocking with pytest-mock, (5) Async testing with pytest-asyncio, (6) Coverage configuration, (7) FastAPI testing with httpx, (8) Property-based testing with hypothesis, (9) Snapshot testing. Provides: pytest patterns, fixture examples, mock setup, coverage config. Ensures comprehensive test coverage with best practices.

Backend & APIs

What this skill does


## Quick Reference

| pytest Command | Purpose |
|----------------|---------|
| `pytest` | Run all tests |
| `pytest -v` | Verbose output |
| `pytest -k "name"` | Run tests matching pattern |
| `pytest -x` | Stop on first failure |
| `pytest --lf` | Run last failed |
| `pytest -n auto` | Parallel execution |

| Fixture Scope | Duration |
|---------------|----------|
| `function` | Per test (default) |
| `class` | Per test class |
| `module` | Per test file |
| `session` | Entire test run |

| Mock Pattern | Code |
|--------------|------|
| Patch function | `mocker.patch("module.func")` |
| Return value | `mock.return_value = {...}` |
| Side effect | `mock.side_effect = [a, b, exc]` |
| Assert called | `mock.assert_called_once()` |

| Marker | Use Case |
|--------|----------|
| `@pytest.mark.asyncio` | Async tests |
| `@pytest.mark.parametrize` | Multiple inputs |
| `@pytest.mark.skip` | Skip test |
| `@pytest.mark.xfail` | Expected failure |

## When to Use This Skill

Use for **testing Python code**:
- Writing pytest tests with fixtures
- Mocking external dependencies
- Testing async code
- Setting up code coverage
- Testing FastAPI applications

**Related skills:**
- For FastAPI: see `python-fastapi`
- For async patterns: see `python-asyncio`
- For CI/CD: see `python-github-actions`

---

# Python Testing Best Practices (2025)

## Overview

Modern Python testing centers around pytest as the de facto standard, with additional tools for coverage, mocking, and async testing.

## Pytest Fundamentals

### Installation

```bash
# With uv
uv add --dev pytest pytest-cov pytest-asyncio pytest-xdist

# With pip
pip install pytest pytest-cov pytest-asyncio pytest-xdist
```

### Basic Test Structure

```python
# tests/test_calculator.py
import pytest
from mypackage.calculator import add, divide

def test_add_positive_numbers():
    assert add(2, 3) == 5

def test_add_negative_numbers():
    assert add(-2, -3) == -5

def test_add_mixed_numbers():
    assert add(-2, 3) == 1

def test_divide_by_zero():
    with pytest.raises(ZeroDivisionError):
        divide(10, 0)

def test_divide_result():
    result = divide(10, 2)
    assert result == pytest.approx(5.0)
```

### Running Tests

```bash
# Run all tests
pytest

# Verbose output
pytest -v

# Run specific file
pytest tests/test_calculator.py

# Run specific test
pytest tests/test_calculator.py::test_add_positive_numbers

# Run tests matching pattern
pytest -k "add"

# Stop on first failure
pytest -x

# Run last failed tests
pytest --lf

# Parallel execution
pytest -n auto
```

## Fixtures

### Basic Fixtures

```python
# tests/conftest.py
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

@pytest.fixture
def sample_user():
    """Provides a sample user dictionary."""
    return {
        "name": "John Doe",
        "email": "[email protected]",
        "age": 30,
    }

@pytest.fixture
def db_session():
    """Provides a database session that rolls back after test."""
    engine = create_engine("sqlite:///:memory:")
    Session = sessionmaker(bind=engine)
    session = Session()

    yield session

    session.rollback()
    session.close()
```

### Fixture Scopes

```python
@pytest.fixture(scope="function")  # Default: new for each test
def per_test_fixture():
    return create_resource()

@pytest.fixture(scope="class")  # Shared within test class
def per_class_fixture():
    return create_expensive_resource()

@pytest.fixture(scope="module")  # Shared within test module
def per_module_fixture():
    return create_very_expensive_resource()

@pytest.fixture(scope="session")  # Shared across entire test session
def per_session_fixture():
    resource = create_global_resource()
    yield resource
    cleanup_global_resource(resource)
```

### Parameterized Fixtures

```python
@pytest.fixture(params=["sqlite", "postgresql", "mysql"])
def database_type(request):
    return request.param

def test_with_multiple_databases(database_type):
    # This test runs 3 times, once for each database type
    db = create_connection(database_type)
    assert db.is_connected()
```

## Parameterized Tests

### Basic Parametrize

```python
import pytest

@pytest.mark.parametrize("input,expected", [
    (1, 1),
    (2, 4),
    (3, 9),
    (4, 16),
])
def test_square(input, expected):
    assert input ** 2 == expected

@pytest.mark.parametrize("a,b,expected", [
    (1, 2, 3),
    (-1, 1, 0),
    (0, 0, 0),
    (100, 200, 300),
])
def test_add(a, b, expected):
    assert add(a, b) == expected
```

### Parametrize with IDs

```python
@pytest.mark.parametrize("input,expected", [
    pytest.param(1, 1, id="one"),
    pytest.param(2, 4, id="two"),
    pytest.param(3, 9, id="three"),
])
def test_square_with_ids(input, expected):
    assert input ** 2 == expected

# Run with: pytest -v
# Shows: test_square_with_ids[one] PASSED
```

### Combining Parametrize

```python
@pytest.mark.parametrize("x", [1, 2])
@pytest.mark.parametrize("y", [3, 4])
def test_combinations(x, y):
    # Runs 4 times: (1,3), (1,4), (2,3), (2,4)
    assert x + y in [4, 5, 6]
```

## Mocking

### Using pytest-mock

```python
from unittest.mock import MagicMock, patch

def test_with_mock(mocker):
    # Mock a method
    mock_api = mocker.patch("mypackage.api.fetch_data")
    mock_api.return_value = {"status": "success"}

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

def test_mock_return_values(mocker):
    # Different return values for successive calls
    mock_func = mocker.patch("mypackage.service.get_item")
    mock_func.side_effect = [
        {"id": 1},
        {"id": 2},
        ValueError("Not found"),
    ]

    assert get_item(1) == {"id": 1}
    assert get_item(2) == {"id": 2}

    with pytest.raises(ValueError):
        get_item(3)
```

### Context Manager Mocking

```python
def test_mock_context_manager(mocker):
    mock_open = mocker.patch("builtins.open", mocker.mock_open(read_data="file content"))

    with open("test.txt") as f:
        content = f.read()

    assert content == "file content"
    mock_open.assert_called_once_with("test.txt")
```

### Mocking Classes

```python
def test_mock_class(mocker):
    MockUser = mocker.patch("mypackage.models.User")
    mock_instance = MockUser.return_value
    mock_instance.name = "Test User"
    mock_instance.save.return_value = True

    user = create_user("Test User")
    assert user.name == "Test User"
    mock_instance.save.assert_called_once()
```

## Async Testing

### pytest-asyncio

```python
import pytest
import asyncio
from httpx import AsyncClient

@pytest.mark.asyncio
async def test_async_function():
    result = await async_fetch_data()
    assert result is not None

@pytest.mark.asyncio
async def test_async_with_fixture(async_client: AsyncClient):
    response = await async_client.get("/api/users")
    assert response.status_code == 200

# Fixture for async client
@pytest.fixture
async def async_client():
    async with AsyncClient(base_url="http://test") as client:
        yield client
```

### Async Fixtures

```python
@pytest.fixture
async def async_db_session():
    engine = create_async_engine("postgresql+asyncpg://...")
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)

    async_session = sessionmaker(engine, class_=AsyncSession)

    async with async_session() as session:
        yield session
        await session.rollback()
```

### Testing FastAPI

```python
import pytest
from httpx import AsyncClient, ASGITransport
from myapp.main import app

@pytest.fixture
async def client():
    async with AsyncClient(
        transport=ASGITransport(app=app),
        base_url="http://test"
    ) as client:
        yield client

@pytest.mark.asyncio
async def test_read_main(client: AsyncClient):
    response = await client.get("/")
    assert response.status_code == 200
    assert response.json() == {"message": "Hello World"}

@pytest.mark.asyncio
async def tes

Related in Backend & APIs