python-testing

Python testing best practices using pytest including fixtures, parametrization, mocking, coverage analysis, async testing, and test organization. Use when writing or improving Python tests.

144,923 stars
Complexity: easy

About this skill

This skill empowers AI agents to generate, review, and refactor Python tests following industry-standard best practices. It leverages the powerful pytest framework to demonstrate and apply advanced testing techniques such as test fixtures for efficient setup/teardown, data parametrization for concise and numerous test cases, mocking for isolating dependencies, comprehensive code coverage analysis, and effective asynchronous testing. The skill also provides guidance on proper test organization to maintain a clean, scalable, and maintainable test suite.

Best use case

Generating new Python test cases for existing functions or modules; Improving the quality, robustness, and coverage of an existing Python test suite; Refactoring poorly written or inefficient Python tests; Learning and applying advanced pytest features; Ensuring code quality and reliability through comprehensive test coverage.

Python testing best practices using pytest including fixtures, parametrization, mocking, coverage analysis, async testing, and test organization. Use when writing or improving Python tests.

High-quality, maintainable, and effective Python tests written with pytest; Improved test coverage and reliability of Python applications; A clear understanding and application of pytest's advanced features; Well-structured and organized test suites; Robust testing for both synchronous and asynchronous Python code, leading to more stable software.

Practical example

Example input

Generate pytest unit tests for the following Python function that calculates the factorial of a number. Include tests for positive integers, zero, and negative inputs, using parametrization where appropriate. Also, demonstrate how to mock a dependency if the function were to make an external call to `requests.get('http://example.com/api/data')`.

Example output

```python
import pytest
from unittest.mock import patch

# Assume this is the function to be tested, located in my_module.py
# def factorial(n):
#     if n < 0:
#         raise ValueError("Factorial is not defined for negative numbers")
#     if n == 0:
#         return 1
#     result = 1
#     for i in range(1, n + 1):
#         result *= i
#     return result

@pytest.mark.parametrize("input_num, expected_output", [
    (0, 1),
    (1, 1),
    (5, 120),
    (7, 5040),
])
def test_factorial_positive_and_zero(input_num, expected_output):
    # Assuming 'factorial' is imported from 'my_module'
    from my_module import factorial
    assert factorial(input_num) == expected_output

def test_factorial_negative_input():
    from my_module import factorial
    with pytest.raises(ValueError, match="Factorial is not defined for negative numbers"):
        factorial(-1)

# Example of mocking an external dependency
# Assume 'some_function_using_external_service' is in my_module.py
# def some_function_using_external_service():
#     import requests
#     response = requests.get("http://example.com/api/data")
#     return response.json()

@patch('requests.get')
def test_some_function_with_mocked_external_service(mock_get):
    from my_module import some_function_using_external_service
    mock_get.return_value.json.return_value = {"key": "mocked_value"}
    result = some_function_using_external_service()
    assert result == {"key": "mocked_value"}
    mock_get.assert_called_once_with("http://example.com/api/data")
```

When to use this skill

  • When asked to write unit or integration tests for Python code; When reviewing Python code and suggesting test improvements; When troubleshooting issues with existing Python tests; When creating a new Python project and needing a solid testing foundation; When working with asynchronous Python code that requires specific testing strategies; When aiming to increase the maintainability and efficiency of a Python test suite.

When not to use this skill

  • When working with testing frameworks other than pytest (e.g., unittest, nose); When the task is unrelated to Python code testing; When the focus is on manual testing or user interface (UI) testing rather than automated unit/integration tests; When the goal is to implement a brand new testing framework from scratch rather than using an existing one.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/python-testing/SKILL.md --create-dirs "https://raw.githubusercontent.com/affaan-m/everything-claude-code/main/.kiro/skills/python-testing/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/python-testing/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How python-testing Compares

Feature / Agentpython-testingStandard Approach
Platform SupportClaudeLimited / Varies
Context Awareness High Baseline
Installation ComplexityeasyN/A

Frequently Asked Questions

What does this skill do?

Python testing best practices using pytest including fixtures, parametrization, mocking, coverage analysis, async testing, and test organization. Use when writing or improving Python tests.

Which AI agents support this skill?

This skill is designed for Claude.

How difficult is it to install?

The installation complexity is rated as easy. You can find the installation instructions above.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

Related Guides

SKILL.md Source

# 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="alice@example.com")

    assert user.name == "Alice"
    assert user.email == "alice@example.com"
    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 pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

@pytest.fixture
def 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="alice@example.com")

    assert user.id is not None
```

### Fixture Scopes

```python
@pytest.fixture(scope="function")  # Default: per test
def user():
    return User(name="Alice")

@pytest.fixture(scope="class")  # Per test class
def database():
    db = Database()
    db.connect()
    yield db
    db.disconnect()

@pytest.fixture(scope="module")  # Per module
def app():
    return create_app()

@pytest.fixture(scope="session")  # Once per test session
def config():
    return load_config()
```

### Fixture Dependencies

```python
@pytest.fixture
def database():
    db = Database()
    db.connect()
    yield db
    db.disconnect()

@pytest.fixture
def 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", [
    ("user@example.com", 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.unit
def test_calculate_total():
    """Fast unit test"""
    assert calculate_total([1, 2, 3]) == 6

@pytest.mark.integration
def test_database_connection():
    """Slower integration test"""
    db = Database()
    assert db.connect() is True

@pytest.mark.slow
def 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 tests
pytest -m "not slow"        # Skip slow tests
pytest -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("user@example.com", "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=html
open 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.asyncio
async def test_async_fetch_user():
    """Test async function"""
    user = await fetch_user("1")
    assert user.name == "Alice"

@pytest.fixture
async def async_client():
    """Async fixture"""
    client = AsyncClient()
    await client.connect()
    yield client
    await client.disconnect()

@pytest.mark.asyncio
async 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.py
import pytest

@pytest.fixture(scope="session")
def app():
    """Application fixture available to all tests"""
    return create_app()

@pytest.fixture
def 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.py
def 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": "test@example.com",
        "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 tests
pytest

# Run specific file
pytest tests/test_user.py

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

# Run with verbose output
pytest -v

# Run with output capture disabled
pytest -s

# Run in parallel (requires pytest-xdist)
pytest -n auto

# Run only failed tests from last run
pytest --lf

# Run failed tests first
pytest --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

Related Skills

swift-protocol-di-testing

144923
from affaan-m/everything-claude-code

基于协议的依赖注入,用于可测试的Swift代码——使用聚焦协议和Swift Testing模拟文件系统、网络和外部API。

DevelopmentClaude

perl-testing

144923
from affaan-m/everything-claude-code

使用Test2::V0、Test::More、prove runner、模拟、Devel::Cover覆盖率和TDD方法的Perl测试模式。

DevelopmentClaude

rust-testing

144923
from affaan-m/everything-claude-code

Rust testing patterns including unit tests, integration tests, async testing, property-based testing, mocking, and coverage. Follows TDD methodology.

DevelopmentClaude

kotlin-testing

144923
from affaan-m/everything-claude-code

Kotest, MockK, coroutine testi, property-based testing ve Kover coverage ile Kotlin test kalıpları. İdiomatic Kotlin uygulamalarıyla TDD metodolojisini takip eder.

DevelopmentClaude

cpp-testing

144923
from affaan-m/everything-claude-code

C++ テストの作成/更新/修正、GoogleTest/CTest の設定、失敗またはフレーキーなテストの診断、カバレッジ/サニタイザーの追加時にのみ使用します。

DevelopmentClaude

python-patterns

144923
from affaan-m/everything-claude-code

Python-specific design patterns and best practices including protocols, dataclasses, context managers, decorators, async/await, type hints, and package organization. Use when working with Python code to apply Pythonic patterns.

DevelopmentClaude

golang-testing

144923
from affaan-m/everything-claude-code

Go testing best practices including table-driven tests, test helpers, benchmarking, race detection, coverage analysis, and integration testing patterns. Use when writing or improving Go tests.

DevelopmentClaude

workspace-surface-audit

144923
from affaan-m/everything-claude-code

Audit the active repo, MCP servers, plugins, connectors, env surfaces, and harness setup, then recommend the highest-value ECC-native skills, hooks, agents, and operator workflows. Use when the user wants help setting up Claude Code or understanding what capabilities are actually available in their environment.

DevelopmentClaude

safety-guard

144923
from affaan-m/everything-claude-code

Use this skill to prevent destructive operations when working on production systems or running agents autonomously.

DevelopmentClaude

repo-scan

144923
from affaan-m/everything-claude-code

Cross-stack source code asset audit — classifies every file, detects embedded third-party libraries, and delivers actionable four-level verdicts per module with interactive HTML reports.

DevelopmentClaude

project-flow-ops

144923
from affaan-m/everything-claude-code

Operate execution flow across GitHub and Linear by triaging issues and pull requests, linking active work, and keeping GitHub public-facing while Linear remains the internal execution layer. Use when the user wants backlog control, PR triage, or GitHub-to-Linear coordination.

DevelopmentClaude

manim-video

144923
from affaan-m/everything-claude-code

Build reusable Manim explainers for technical concepts, graphs, system diagrams, and product walkthroughs, then hand off to the wider ECC video stack if needed. Use when the user wants a clean animated explainer rather than a generic talking-head script.

DevelopmentClaude