Source profileQuality 87/100

affaan-m/ECC/skills/python-testing/SKILL.md

python-testing

Python testing strategies using pytest, TDD methodology, fixtures, mocking, parametrization, and coverage requirements.

Source repository stars
234,327
Declared platforms
0
Static risk flags
3
Last source update
2026-07-27
Source checked
2026-07-28

Decision brief

What it does—and where it fits

Comprehensive testing strategies for Python applications using pytest, TDD methodology, and best practices.

Best for

    Not for

    • Tasks that require unconfirmed production actions or broad system permissions.
    • Environments where the pinned source and install steps cannot be inspected.

    Compatibility matrix

    Platform support, with evidence labels

    PlatformStatusEvidenceWhat to check
    CodexNot declaredNo explicit evidencePortability before use
    Claude CodeNot declaredNo explicit evidencePortability before use
    CursorNot declaredNo explicit evidencePortability before use
    Gemini CLINot declaredNo explicit evidencePortability before use
    Open the compatibility checker

    Installation

    Inspect first. Install second.

    The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

    Source-detected install commandSource
    npx skills add https://github.com/affaan-m/ECC --skill "skills/python-testing"
    Safe inspection promptEditorial

    Inspect the Agent Skill "python-testing" from https://github.com/affaan-m/ECC/blob/4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38/skills/python-testing/SKILL.md at commit 4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.

    Workflow

    What the source asks the agent to do

    1. 01

      Step 1: Write failing test (RED)

      def testaddnumbers(): result = add(2, 3) assert result == 5

      def testaddnumbers(): result = add(2, 3) assert result == 5
    2. 02

      Step 2: Write minimal implementation (GREEN)

      def add(a, b): return a + b

      def add(a, b): return a + b
    3. 03

      Step 3: Refactor if needed (REFACTOR)

      bash pytest --cov=mypackage --cov-report=term-missing --cov-report=html python import pytest

      bash pytest --cov=mypackage --cov-report=term-missing --cov-report=html python import pytestdef testaddition(): """Test basic addition.""" assert 2 + 2 == 4def teststringuppercase(): """Test string uppercasing.""" text = "hello" assert text.upper() == "HELLO"
    4. 04

      Basic Fixture Usage

      Review the “Basic Fixture Usage” section in the pinned source before continuing.

      Review and apply the “Basic Fixture Usage” source section.
    5. 05

      Fixture with Setup/Teardown

      Review the “Fixture with Setup/Teardown” section in the pinned source before continuing.

      Review and apply the “Fixture with Setup/Teardown” source section.

    Permission review

    Static risk signals and limitations

    Network access

    medium · line 318

    The documentation includes network, browsing, or remote request actions.

    response = requests.get("https://api.example.com")

    Reads files

    low · line 406

    The documentation asks the agent to read local files, directories, or repositories.

    mock_file.return_value.read.return_value = "file content"

    Writes files

    medium · line 558

    The documentation asks the agent to create, modify, or delete local files.

    os.unlink(temp_path)

    Evidence record

    Why each signal appears

    EvidenceSourceComputedTestedEditorial
    SignalValueEvidence typeMeaning
    Quality score87/100ComputedDocumentation, specificity, maintenance, and trust rules
    Repository stars234,327SourceRepository attention, not individual Skill quality
    Compatibility0 platformsSourceDeclared in the catalog source record
    Usage guideautomated source guideEditorialGenerated or reviewed according to the visible evidence level

    Pinned source

    Provenance and original SKILL.md

    Repository
    affaan-m/ECC
    Skill path
    skills/python-testing/SKILL.md
    Commit
    4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38
    License
    MIT
    Collected
    2026-07-28
    Default branch
    main
    View the original SKILL.md

    Python Testing Patterns

    Comprehensive testing strategies for Python applications using pytest, TDD methodology, and best practices.

    When to Activate

    • Writing new Python code (follow TDD: red, green, refactor)
    • Designing test suites for Python projects
    • Reviewing Python test coverage
    • Setting up testing infrastructure

    Core Testing Philosophy

    Test-Driven Development (TDD)

    Always follow the TDD cycle:

    1. RED: Write a failing test for the desired behavior
    2. GREEN: Write minimal code to make the test pass
    3. REFACTOR: Improve code while keeping tests green
    # Step 1: Write failing test (RED)
    def test_add_numbers():
        result = add(2, 3)
        assert result == 5
    
    # Step 2: Write minimal implementation (GREEN)
    def add(a, b):
        return a + b
    
    # Step 3: Refactor if needed (REFACTOR)
    

    Coverage Requirements

    • Target: 80%+ code coverage
    • Critical paths: 100% coverage required
    • Use pytest --cov to measure coverage
    pytest --cov=mypackage --cov-report=term-missing --cov-report=html
    

    pytest Fundamentals

    Basic Test Structure

    import pytest
    
    def test_addition():
        """Test basic addition."""
        assert 2 + 2 == 4
    
    def test_string_uppercase():
        """Test string uppercasing."""
        text = "hello"
        assert text.upper() == "HELLO"
    
    def test_list_append():
        """Test list append."""
        items = [1, 2, 3]
        items.append(4)
        assert 4 in items
        assert len(items) == 4
    

    Assertions

    # Equality
    assert result == expected
    
    # Inequality
    assert result != unexpected
    
    # Truthiness
    assert result  # Truthy
    assert not result  # Falsy
    assert result is True  # Exactly True
    assert result is False  # Exactly False
    assert result is None  # Exactly None
    
    # Membership
    assert item in collection
    assert item not in collection
    
    # Comparisons
    assert result > 0
    assert 0 <= result <= 100
    
    # Type checking
    assert isinstance(result, str)
    
    # Exception testing (preferred approach)
    with pytest.raises(ValueError):
        raise ValueError("error message")
    
    # Check exception message
    with pytest.raises(ValueError, match="invalid input"):
        raise ValueError("invalid input provided")
    
    # Check exception attributes
    with pytest.raises(ValueError) as exc_info:
        raise ValueError("error message")
    assert str(exc_info.value) == "error message"
    

    Fixtures

    Basic Fixture Usage

    import pytest
    
    @pytest.fixture
    def sample_data():
        """Fixture providing sample data."""
        return {"name": "Alice", "age": 30}
    
    def test_sample_data(sample_data):
        """Test using the fixture."""
        assert sample_data["name"] == "Alice"
        assert sample_data["age"] == 30
    

    Fixture with Setup/Teardown

    @pytest.fixture
    def database():
        """Fixture with setup and teardown."""
        # Setup
        db = Database(":memory:")
        db.create_tables()
        db.insert_test_data()
    
        yield db  # Provide to test
    
        # Teardown
        db.close()
    
    def test_database_query(database):
        """Test database operations."""
        result = database.query("SELECT * FROM users")
        assert len(result) > 0
    

    Fixture Scopes

    # Function scope (default) - runs for each test
    @pytest.fixture
    def temp_file():
        with open("temp.txt", "w") as f:
            yield f
        os.remove("temp.txt")
    
    # Module scope - runs once per module
    @pytest.fixture(scope="module")
    def module_db():
        db = Database(":memory:")
        db.create_tables()
        yield db
        db.close()
    
    # Session scope - runs once per test session
    @pytest.fixture(scope="session")
    def shared_resource():
        resource = ExpensiveResource()
        yield resource
        resource.cleanup()
    

    Fixture with Parameters

    @pytest.fixture(params=[1, 2, 3])
    def number(request):
        """Parameterized fixture."""
        return request.param
    
    def test_numbers(number):
        """Test runs 3 times, once for each parameter."""
        assert number > 0
    

    Using Multiple Fixtures

    @pytest.fixture
    def user():
        return User(id=1, name="Alice")
    
    @pytest.fixture
    def admin():
        return User(id=2, name="Admin", role="admin")
    
    def test_user_admin_interaction(user, admin):
        """Test using multiple fixtures."""
        assert admin.can_manage(user)
    

    Autouse Fixtures

    @pytest.fixture(autouse=True)
    def reset_config():
        """Automatically runs before every test."""
        Config.reset()
        yield
        Config.cleanup()
    
    def test_without_fixture_call():
        # reset_config runs automatically
        assert Config.get_setting("debug") is False
    

    Conftest.py for Shared Fixtures

    # tests/conftest.py
    import pytest
    
    @pytest.fixture
    def client():
        """Shared fixture for all tests."""
        app = create_app(testing=True)
        with app.test_client() as client:
            yield client
    
    @pytest.fixture
    def auth_headers(client):
        """Generate auth headers for API testing."""
        response = client.post("/api/login", json={
            "username": "test",
            "password": "test"
        })
        token = response.json["token"]
        return {"Authorization": f"Bearer {token}"}
    

    Parametrization

    Basic Parametrization

    @pytest.mark.parametrize("input,expected", [
        ("hello", "HELLO"),
        ("world", "WORLD"),
        ("PyThOn", "PYTHON"),
    ])
    def test_uppercase(input, expected):
        """Test runs 3 times with different inputs."""
        assert input.upper() == expected
    

    Multiple Parameters

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

    Parametrize with IDs

    @pytest.mark.parametrize("input,expected", [
        ("valid@email.com", True),
        ("invalid", False),
        ("@no-domain.com", False),
    ], ids=["valid-email", "missing-at", "missing-domain"])
    def test_email_validation(input, expected):
        """Test email validation with readable test IDs."""
        assert is_valid_email(input) is expected
    

    Parametrized Fixtures

    @pytest.fixture(params=["sqlite", "postgresql", "mysql"])
    def db(request):
        """Test against multiple database backends."""
        if request.param == "sqlite":
            return Database(":memory:")
        elif request.param == "postgresql":
            return Database("postgresql://localhost/test")
        elif request.param == "mysql":
            return Database("mysql://localhost/test")
    
    def test_database_operations(db):
        """Test runs 3 times, once for each database."""
        result = db.query("SELECT 1")
        assert result is not None
    

    Markers and Test Selection

    Custom Markers

    # Mark slow tests
    @pytest.mark.slow
    def test_slow_operation():
        time.sleep(5)
    
    # Mark integration tests
    @pytest.mark.integration
    def test_api_integration():
        response = requests.get("https://api.example.com")
        assert response.status_code == 200
    
    # Mark unit tests
    @pytest.mark.unit
    def test_unit_logic():
        assert calculate(2, 3) == 5
    

    Run Specific Tests

    # Run only fast tests
    pytest -m "not slow"
    
    # Run only integration tests
    pytest -m integration
    
    # Run integration or slow tests
    pytest -m "integration or slow"
    
    # Run tests marked as unit but not slow
    pytest -m "unit and not slow"
    

    Configure Markers in pytest.ini

    [pytest]
    markers =
        slow: marks tests as slow
        integration: marks tests as integration tests
        unit: marks tests as unit tests
        django: marks tests as requiring Django
    

    Mocking and Patching

    Mocking Functions

    from unittest.mock import patch, Mock
    
    @patch("mypackage.external_api_call")
    def test_with_mock(api_call_mock):
        """Test with mocked external API."""
        api_call_mock.return_value = {"status": "success"}
    
        result = my_function()
    
        api_call_mock.assert_called_once()
        assert result["status"] == "success"
    

    Mocking Return Values

    @patch("mypackage.Database.connect")
    def test_database_connection(connect_mock):
        """Test with mocked database connection."""
        connect_mock.return_value = MockConnection()
    
        db = Database()
        db.connect()
    
        connect_mock.assert_called_once_with("localhost")
    

    Mocking Exceptions

    @patch("mypackage.api_call")
    def test_api_error_handling(api_call_mock):
        """Test error handling with mocked exception."""
        api_call_mock.side_effect = ConnectionError("Network error")
    
        with pytest.raises(ConnectionError):
            api_call()
    
        api_call_mock.assert_called_once()
    

    Mocking Context Managers

    @patch("builtins.open", new_callable=mock_open)
    def test_file_reading(mock_file):
        """Test file reading with mocked open."""
        mock_file.return_value.read.return_value = "file content"
    
        result = read_file("test.txt")
    
        mock_file.assert_called_once_with("test.txt", "r")
        assert result == "file content"
    

    Using Autospec

    @patch("mypackage.DBConnection", autospec=True)
    def test_autospec(db_mock):
        """Test with autospec to catch API misuse."""
        db = db_mock.return_value
        db.query("SELECT * FROM users")
    
        # This would fail if DBConnection doesn't have query method
        db_mock.assert_called_once()
    

    Mock Class Instances

    class TestUserService:
        @patch("mypackage.UserRepository")
        def test_create_user(self, repo_mock):
            """Test user creation with mocked repository."""
            repo_mock.return_value.save.return_value = User(id=1, name="Alice")
    
            service = UserService(repo_mock.return_value)
            user = service.create_user(name="Alice")
    
            assert user.name == "Alice"
            repo_mock.return_value.save.assert_called_once()
    

    Mock Property

    @pytest.fixture
    def mock_config():
        """Create a mock with a property."""
        config = Mock()
        type(config).debug = PropertyMock(return_value=True)
        type(config).api_key = PropertyMock(return_value="test-key")
        return config
    
    def test_with_mock_config(mock_config):
        """Test with mocked config properties."""
        assert mock_config.debug is True
        assert mock_config.api_key == "test-key"
    

    Testing Async Code

    Async Tests with pytest-asyncio

    import pytest
    
    @pytest.mark.asyncio
    async def test_async_function():
        """Test async function."""
        result = await async_add(2, 3)
        assert result == 5
    
    @pytest.mark.asyncio
    async def test_async_with_fixture(async_client):
        """Test async with async fixture."""
        response = await async_client.get("/api/users")
        assert response.status_code == 200
    

    Async Fixture

    @pytest.fixture
    async def async_client():
        """Async fixture providing async test client."""
        app = create_app()
        async with app.test_client() as client:
            yield client
    
    @pytest.mark.asyncio
    async def test_api_endpoint(async_client):
        """Test using async fixture."""
        response = await async_client.get("/api/data")
        assert response.status_code == 200
    

    Mocking Async Functions

    @pytest.mark.asyncio
    @patch("mypackage.async_api_call")
    async def test_async_mock(api_call_mock):
        """Test async function with mock."""
        api_call_mock.return_value = {"status": "ok"}
    
        result = await my_async_function()
    
        api_call_mock.assert_awaited_once()
        assert result["status"] == "ok"
    

    Testing Exceptions

    Testing Expected Exceptions

    def test_divide_by_zero():
        """Test that dividing by zero raises ZeroDivisionError."""
        with pytest.raises(ZeroDivisionError):
            divide(10, 0)
    
    def test_custom_exception():
        """Test custom exception with message."""
        with pytest.raises(ValueError, match="invalid input"):
            validate_input("invalid")
    

    Testing Exception Attributes

    def test_exception_with_details():
        """Test exception with custom attributes."""
        with pytest.raises(CustomError) as exc_info:
            raise CustomError("error", code=400)
    
        assert exc_info.value.code == 400
        assert "error" in str(exc_info.value)
    

    Testing Side Effects

    Testing File Operations

    import tempfile
    import os
    
    def test_file_processing():
        """Test file processing with temp file."""
        with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:
            f.write("test content")
            temp_path = f.name
    
        try:
            result = process_file(temp_path)
            assert result == "processed: test content"
        finally:
            os.unlink(temp_path)
    

    Testing with pytest's tmp_path Fixture

    def test_with_tmp_path(tmp_path):
        """Test using pytest's built-in temp path fixture."""
        test_file = tmp_path / "test.txt"
        test_file.write_text("hello world")
    
        result = process_file(str(test_file))
        assert result == "hello world"
        # tmp_path automatically cleaned up
    

    Testing with tmpdir Fixture

    def test_with_tmpdir(tmpdir):
        """Test using pytest's tmpdir fixture."""
        test_file = tmpdir.join("test.txt")
        test_file.write("data")
    
        result = process_file(str(test_file))
        assert result == "data"
    

    Test Organization

    Directory Structure

    tests/
    ├── conftest.py                 # Shared fixtures
    ├── __init__.py
    ├── unit/                       # Unit tests
    │   ├── __init__.py
    │   ├── test_models.py
    │   ├── test_utils.py
    │   └── test_services.py
    ├── integration/                # Integration tests
    │   ├── __init__.py
    │   ├── test_api.py
    │   └── test_database.py
    └── e2e/                        # End-to-end tests
        ├── __init__.py
        └── test_user_flow.py
    

    Test Classes

    class TestUserService:
        """Group related tests in a class."""
    
        @pytest.fixture(autouse=True)
        def setup(self):
            """Setup runs before each test in this class."""
            self.service = UserService()
    
        def test_create_user(self):
            """Test user creation."""
            user = self.service.create_user("Alice")
            assert user.name == "Alice"
    
        def test_delete_user(self):
            """Test user deletion."""
            user = User(id=1, name="Bob")
            self.service.delete_user(user)
            assert not self.service.user_exists(1)
    

    Best Practices

    DO

    • Follow TDD: Write tests before code (red-green-refactor)
    • Test one thing: Each test should verify a single behavior
    • Use descriptive names: test_user_login_with_invalid_credentials_fails
    • Use fixtures: Eliminate duplication with fixtures
    • Mock external dependencies: Don't depend on external services
    • Test edge cases: Empty inputs, None values, boundary conditions
    • Aim for 80%+ coverage: Focus on critical paths
    • Keep tests fast: Use marks to separate slow tests

    DON'T

    • Don't test implementation: Test behavior, not internals
    • Don't use complex conditionals in tests: Keep tests simple
    • Don't ignore test failures: All tests must pass
    • Don't test third-party code: Trust libraries to work
    • Don't share state between tests: Tests should be independent
    • Don't catch exceptions in tests: Use pytest.raises
    • Don't use print statements: Use assertions and pytest output
    • Don't write tests that are too brittle: Avoid over-specific mocks

    Common Patterns

    Testing API Endpoints (FastAPI/Flask)

    @pytest.fixture
    def client():
        app = create_app(testing=True)
        return app.test_client()
    
    def test_get_user(client):
        response = client.get("/api/users/1")
        assert response.status_code == 200
        assert response.json["id"] == 1
    
    def test_create_user(client):
        response = client.post("/api/users", json={
            "name": "Alice",
            "email": "alice@example.com"
        })
        assert response.status_code == 201
        assert response.json["name"] == "Alice"
    

    Testing Database Operations

    @pytest.fixture
    def db_session():
        """Create a test database session."""
        session = Session(bind=engine)
        session.begin_nested()
        yield session
        session.rollback()
        session.close()
    
    def test_create_user(db_session):
        user = User(name="Alice", email="alice@example.com")
        db_session.add(user)
        db_session.commit()
    
        retrieved = db_session.query(User).filter_by(name="Alice").first()
        assert retrieved.email == "alice@example.com"
    

    Testing Class Methods

    class TestCalculator:
        @pytest.fixture
        def calculator(self):
            return Calculator()
    
        def test_add(self, calculator):
            assert calculator.add(2, 3) == 5
    
        def test_divide_by_zero(self, calculator):
            with pytest.raises(ZeroDivisionError):
                calculator.divide(10, 0)
    

    pytest Configuration

    pytest.ini

    [pytest]
    testpaths = tests
    python_files = test_*.py
    python_classes = Test*
    python_functions = test_*
    addopts =
        --strict-markers
        --disable-warnings
        --cov=mypackage
        --cov-report=term-missing
        --cov-report=html
    markers =
        slow: marks tests as slow
        integration: marks tests as integration tests
        unit: marks tests as unit tests
    

    pyproject.toml

    [tool.pytest.ini_options]
    testpaths = ["tests"]
    python_files = ["test_*.py"]
    python_classes = ["Test*"]
    python_functions = ["test_*"]
    addopts = [
        "--strict-markers",
        "--cov=mypackage",
        "--cov-report=term-missing",
        "--cov-report=html",
    ]
    markers = [
        "slow: marks tests as slow",
        "integration: marks tests as integration tests",
        "unit: marks tests as unit tests",
    ]
    

    Running Tests

    # Run all tests
    pytest
    
    # Run specific file
    pytest tests/test_utils.py
    
    # Run specific test
    pytest tests/test_utils.py::test_function
    
    # Run with verbose output
    pytest -v
    
    # Run with coverage
    pytest --cov=mypackage --cov-report=html
    
    # Run only fast tests
    pytest -m "not slow"
    
    # Run until first failure
    pytest -x
    
    # Run and stop on N failures
    pytest --maxfail=3
    
    # Run last failed tests
    pytest --lf
    
    # Run tests with pattern
    pytest -k "test_user"
    
    # Run with debugger on failure
    pytest --pdb
    

    Quick Reference

    PatternUsage
    pytest.raises()Test expected exceptions
    @pytest.fixture()Create reusable test fixtures
    @pytest.mark.parametrize()Run tests with multiple inputs
    @pytest.mark.slowMark slow tests
    pytest -m "not slow"Skip slow tests
    @patch()Mock functions and classes
    tmp_path fixtureAutomatic temp directory
    pytest --covGenerate coverage report
    assertSimple and readable assertions

    Remember: Tests are code too. Keep them clean, readable, and maintainable. Good tests catch bugs; great tests prevent them.

    Alternatives

    Compare before choosing