Python Testing

affaan-m/ECC/.kiro/skills/python-testing

by affaan-mef648e01899ba3e8dc6371642deaaf64b4477775No license275K starsListed Oct 9, 2026Updated Oct 9, 2026Repository updated 4 days ago

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

Instructions onlySoftware Development
AI-generated overview

Guidance on Python testing with pytest, covering fixtures, parametrization, mocking, coverage, and async tests.

What it does
This skill supplies reference patterns and conventions for writing Python tests with pytest. It covers test discovery, fixtures and their scopes, parametrization, markers, mocking with unittest.mock and pytest-mock, coverage reporting, async testing, exception assertions, property-based testing with hypothesis, and test directory organization. It produces guidance and code examples rather than executable tooling.
When to use it
Use it when writing new Python tests, improving test coverage, or setting up pytest infrastructure. It also fits debugging flaky tests, adding integration tests, and testing async Python code.
Requirements
No scripts are included; the content is instructions and code examples only. Following the examples assumes a Python environment with pytest installed, plus optional packages such as pytest-cov, pytest-mock, pytest-asyncio, pytest-xdist, hypothesis, and SQLAlchemy for the database fixture examples.

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="[email protected]")
    assert user.name == "Alice"    assert user.email == "[email protected]"    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 pytestfrom sqlalchemy import create_enginefrom sqlalchemy.orm import sessionmaker
@pytest.fixturedef 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="[email protected]")
    assert user.id is not None

Fixture Scopes

python
@pytest.fixture(scope="function")  # Default: per testdef user():    return User(name="Alice")
@pytest.fixture(scope="class")  # Per test classdef database():    db = Database()    db.connect()    yield db    db.disconnect()
@pytest.fixture(scope="module")  # Per moduledef app():    return create_app()
@pytest.fixture(scope="session")  # Once per test sessiondef config():    return load_config()

Fixture Dependencies

python
@pytest.fixturedef database():    db = Database()    db.connect()    yield db    db.disconnect()
@pytest.fixturedef 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", [    ("[email protected]", 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.unitdef test_calculate_total():    """Fast unit test"""    assert calculate_total([1, 2, 3]) == 6
@pytest.mark.integrationdef test_database_connection():    """Slower integration test"""    db = Database()    assert db.connect() is True
@pytest.mark.slowdef 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 testspytest -m "not slow"        # Skip slow testspytest -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("[email protected]", "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=htmlopen 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.asyncioasync def test_async_fetch_user():    """Test async function"""    user = await fetch_user("1")    assert user.name == "Alice"
@pytest.fixtureasync def async_client():    """Async fixture"""    client = AsyncClient()    await client.connect()    yield client    await client.disconnect()
@pytest.mark.asyncioasync 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.pyimport pytest
@pytest.fixture(scope="session")def app():    """Application fixture available to all tests"""    return create_app()
@pytest.fixturedef 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.pydef 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": "[email protected]",        "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 testspytest
# Run specific filepytest tests/test_user.py
# Run specific testpytest tests/test_user.py::test_create_user
# Run with verbose outputpytest -v
# Run with output capture disabledpytest -s
# Run in parallel (requires pytest-xdist)pytest -n auto
# Run only failed tests from last runpytest --lf
# Run failed tests firstpytest --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

Source and attribution

Source:affaan-m/ECCin.kiro/skills/python-testingat commitef648e0

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal