Agent skill

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.

Stars 132,726
Forks 19,206

Install this agent skill to your Project

npx add-skill https://github.com/affaan-m/everything-claude-code/tree/main/.kiro/skills/python-testing

Metadata

Additional technical details for this skill

globs
[
    "**/*.py",
    "**/*.pyi"
]
origin
ECC

SKILL.md

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

Expand your agent's capabilities with these related and highly-rated skills.

affaan-m/everything-claude-code

golang-patterns

Go-specific design patterns and best practices including functional options, small interfaces, dependency injection, concurrency patterns, error handling, and package organization. Use when working with Go code to apply idiomatic Go patterns.

132,726 19,206
Explore
affaan-m/everything-claude-code

e2e-testing

Playwright E2E testing patterns, Page Object Model, configuration, CI/CD integration, artifact management, and flaky test strategies.

132,726 19,206
Explore
affaan-m/everything-claude-code

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when AI agents perform most implementation work and humans enforce quality and risk controls.

132,726 19,206
Explore
affaan-m/everything-claude-code

api-design

REST API design patterns including resource naming, status codes, pagination, filtering, error responses, versioning, and rate limiting for production APIs.

132,726 19,206
Explore
affaan-m/everything-claude-code

python-patterns

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.

132,726 19,206
Explore
affaan-m/everything-claude-code

golang-testing

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.

132,726 19,206
Explore

Didn't find tool you were looking for?

Be as detailed as possible for better results