Claude
Skills
Sign in
Back

pytest

Included with Lifetime
$97 forever

pytest - Python's most powerful testing framework with fixtures, parametrization, plugins, and framework integration for FastAPI, Django, Flask

toolchainpytesttestingpythontddunit-testingfixturesmockingasync

What this skill does


# pytest - Professional Python Testing

## Overview

pytest is the industry-standard Python testing framework, offering powerful features like fixtures, parametrization, markers, plugins, and seamless integration with FastAPI, Django, and Flask. It provides a simple, scalable approach to testing from unit tests to complex integration scenarios.

**Key Features**:
- Fixture system for dependency injection
- Parametrization for data-driven tests
- Rich assertion introspection (no need for `self.assertEqual`)
- Plugin ecosystem (pytest-cov, pytest-asyncio, pytest-mock, pytest-django)
- Async/await support
- Parallel test execution with pytest-xdist
- Test discovery and organization
- Detailed failure reporting

**Installation**:
```bash
# Basic pytest
pip install pytest

# With common plugins
pip install pytest pytest-cov pytest-asyncio pytest-mock

# For FastAPI testing
pip install pytest httpx pytest-asyncio

# For Django testing
pip install pytest pytest-django

# For async databases
pip install pytest-asyncio aiosqlite
```

## Basic Testing Patterns

### 1. Simple Test Functions

```python
# test_math.py
def add(a, b):
    return a + b

def test_add():
    assert add(2, 3) == 5
    assert add(-1, 1) == 0
    assert add(0, 0) == 0

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

**Run tests:**
```bash
# Discover and run all tests
pytest

# Verbose output
pytest -v

# Show print statements
pytest -s

# Run specific test file
pytest test_math.py

# Run specific test function
pytest test_math.py::test_add
```

### 2. Test Classes for Organization

```python
# test_calculator.py
class Calculator:
    def add(self, a, b):
        return a + b

    def multiply(self, a, b):
        return a * b

class TestCalculator:
    def test_add(self):
        calc = Calculator()
        assert calc.add(2, 3) == 5

    def test_multiply(self):
        calc = Calculator()
        assert calc.multiply(4, 5) == 20

    def test_add_negative(self):
        calc = Calculator()
        assert calc.add(-1, -1) == -2
```

### 3. Assertions and Expected Failures

```python
import pytest

# Test exception raising
def divide(a, b):
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b

def test_divide_by_zero():
    with pytest.raises(ValueError, match="Cannot divide by zero"):
        divide(10, 0)

def test_divide_success():
    assert divide(10, 2) == 5.0

# Test approximate equality
def test_float_comparison():
    assert 0.1 + 0.2 == pytest.approx(0.3)

# Test containment
def test_list_contains():
    result = [1, 2, 3, 4]
    assert 3 in result
    assert len(result) == 4
```

## Fixtures - Dependency Injection

### Basic Fixtures

```python
# conftest.py
import pytest

@pytest.fixture
def sample_data():
    """Provide sample data for tests."""
    return {"name": "Alice", "age": 30, "email": "[email protected]"}

@pytest.fixture
def empty_list():
    """Provide an empty list."""
    return []

# test_fixtures.py
def test_sample_data(sample_data):
    assert sample_data["name"] == "Alice"
    assert sample_data["age"] == 30

def test_empty_list(empty_list):
    empty_list.append(1)
    assert len(empty_list) == 1
```

### Fixture Scopes

```python
import pytest

# Function scope (default) - runs for each test
@pytest.fixture(scope="function")
def user():
    return {"id": 1, "name": "Alice"}

# Class scope - runs once per test class
@pytest.fixture(scope="class")
def database():
    db = setup_database()
    yield db
    db.close()

# Module scope - runs once per test module
@pytest.fixture(scope="module")
def api_client():
    client = APIClient()
    yield client
    client.shutdown()

# Session scope - runs once for entire test session
@pytest.fixture(scope="session")
def app_config():
    return load_config()
```

### Fixture Setup and Teardown

```python
import pytest
import tempfile
import shutil

@pytest.fixture
def temp_directory():
    """Create a temporary directory for test."""
    temp_dir = tempfile.mkdtemp()
    print(f"
Setup: Created {temp_dir}")

    yield temp_dir  # Provide directory to test

    # Teardown: cleanup after test
    shutil.rmtree(temp_dir)
    print(f"
Teardown: Removed {temp_dir}")

def test_file_creation(temp_directory):
    file_path = f"{temp_directory}/test.txt"
    with open(file_path, "w") as f:
        f.write("test content")

    assert os.path.exists(file_path)
```

### Fixture Dependencies

```python
import pytest

@pytest.fixture
def database_connection():
    """Database connection."""
    conn = connect_to_db()
    yield conn
    conn.close()

@pytest.fixture
def database_session(database_connection):
    """Database session depends on connection."""
    session = create_session(database_connection)
    yield session
    session.rollback()
    session.close()

@pytest.fixture
def user_repository(database_session):
    """User repository depends on session."""
    return UserRepository(database_session)

def test_create_user(user_repository):
    user = user_repository.create(name="Alice", email="[email protected]")
    assert user.name == "Alice"
```

## Parametrization - Data-Driven Testing

### Basic Parametrization

```python
import pytest

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

### Multiple Parameters

```python
@pytest.mark.parametrize("operation,a,b,expected", [
    ("add", 2, 3, 5),
    ("subtract", 10, 5, 5),
    ("multiply", 4, 5, 20),
    ("divide", 10, 2, 5),
])
def test_calculator_operations(operation, a, b, expected):
    calc = Calculator()
    result = getattr(calc, operation)(a, b)
    assert result == expected
```

### Parametrize with IDs

```python
@pytest.mark.parametrize("input_data,expected", [
    pytest.param({"name": "Alice"}, "Alice", id="valid_name"),
    pytest.param({"name": ""}, None, id="empty_name"),
    pytest.param({}, None, id="missing_name"),
], ids=lambda x: x if isinstance(x, str) else None)
def test_extract_name(input_data, expected):
    result = extract_name(input_data)
    assert result == expected
```

### Indirect Parametrization (Fixtures)

```python
@pytest.fixture
def user_data(request):
    """Create user based on parameter."""
    return {"name": request.param, "email": f"{request.param}@example.com"}

@pytest.mark.parametrize("user_data", ["Alice", "Bob", "Charlie"], indirect=True)
def test_user_creation(user_data):
    assert "@example.com" in user_data["email"]
```

## Test Markers

### Built-in Markers

```python
import pytest

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

# Skip conditionally
@pytest.mark.skipif(sys.platform == "win32", reason="Unix-only test")
def test_unix_specific():
    pass

# Expected failure
@pytest.mark.xfail(reason="Known bug #123")
def test_known_bug():
    assert False

# Slow test marker
@pytest.mark.slow
def test_expensive_operation():
    time.sleep(5)
    assert True
```

### Custom Markers

```python
# 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
    smoke: marks tests as smoke tests

# test_custom_markers.py
import pytest

@pytest.mark.unit
def test_fast_unit():
    assert True

@pytest.mark.integration
@pytest.mark.slow
def test_slow_integration():
    # Integration test with database
    pass

@pytest.mark.smoke
def test_critical_path():
    # Smoke test for critical functionality
    pass
```

**Run tests by marker:**
```bash
# Run only unit tests
pytest -m unit

# Run all except slow tests
pytest -m "not slow"

# Run integration tests
pytest -m integration

# Run unit AND integration
pytest -m "unit or integration"

# Run smoke tests only
pytest -m smoke
```

## FastAPI Testing

### Basic FastAPI Test Setup

```python
# app/main.py
from fastapi i

Related in toolchain