Skip to content

Diagrams: Testing Strategies

Back to concept


The Testing Pyramid

More unit tests at the base, fewer integration tests in the middle, and a small number of end-to-end tests at the top. The pyramid reflects speed, cost, and quantity.

flowchart TD
    subgraph PYRAMID ["Testing Pyramid"]
        E2E["End-to-End Tests<br/>Few, slow, expensive<br/>Test full user workflows"]
        INT["Integration Tests<br/>Moderate count<br/>Test components working together"]
        UNIT["Unit Tests<br/>Many, fast, cheap<br/>Test individual functions"]
    end

    E2E --- INT --- UNIT

    subgraph TRAITS ["Characteristics"]
        direction LR
        SPEED["Speed: Unit > Integration > E2E"]
        COUNT["Count: Unit > Integration > E2E"]
        SCOPE["Scope: E2E > Integration > Unit"]
        COST["Cost: E2E > Integration > Unit"]
    end

    style E2E fill:#ff6b6b,stroke:#c92a2a,color:#fff
    style INT fill:#ffd43b,stroke:#f59f00,color:#000
    style UNIT fill:#51cf66,stroke:#27ae60,color:#fff
    style TRAITS fill:#4a9eff,stroke:#2670c2,color:#fff

Test Types Compared

What each test type covers and how they differ in scope.

flowchart LR
    subgraph UNIT_TEST ["Unit Test"]
        UF["Test one function<br/>in isolation"]
        UF --> UM["Mock all<br/>dependencies"]
        UM --> UR["Assert return value<br/>or side effect"]
    end

    subgraph INT_TEST ["Integration Test"]
        IF["Test multiple<br/>components together"]
        IF --> ID["Real database<br/>or file system"]
        ID --> IR["Assert correct<br/>interaction"]
    end

    subgraph E2E_TEST ["End-to-End Test"]
        EF["Test full workflow<br/>as a user would"]
        EF --> ES["Real server,<br/>real browser"]
        ES --> ER["Assert user-visible<br/>outcome"]
    end

    subgraph EXAMPLE ["Example: User signup"]
        EX_U["Unit: hash_password()<br/>returns correct hash"]
        EX_I["Integration: create_user()<br/>writes to real database"]
        EX_E["E2E: Fill signup form,<br/>click submit, see dashboard"]
    end

    style UNIT_TEST fill:#51cf66,stroke:#27ae60,color:#fff
    style INT_TEST fill:#ffd43b,stroke:#f59f00,color:#000
    style E2E_TEST fill:#ff6b6b,stroke:#c92a2a,color:#fff
    style EXAMPLE fill:#4a9eff,stroke:#2670c2,color:#fff

TDD Cycle: Red-Green-Refactor

Test-Driven Development follows a strict cycle: write a failing test first, make it pass with minimal code, then clean up.

flowchart TD
    RED["RED<br/>Write a failing test<br/>for the next feature"] --> GREEN["GREEN<br/>Write the simplest code<br/>that makes the test pass"]
    GREEN --> REFACTOR["REFACTOR<br/>Clean up the code<br/>without changing behavior"]
    REFACTOR --> CHECK{"All tests<br/>still pass?"}
    CHECK -->|"Yes"| NEXT{"More features<br/>to build?"}
    CHECK -->|"No"| FIX["Fix the code<br/>until tests pass"]
    FIX --> CHECK
    NEXT -->|"Yes"| RED
    NEXT -->|"No"| DONE["Done!"]

    style RED fill:#ff6b6b,stroke:#c92a2a,color:#fff
    style GREEN fill:#51cf66,stroke:#27ae60,color:#fff
    style REFACTOR fill:#4a9eff,stroke:#2670c2,color:#fff
    style CHECK fill:#ffd43b,stroke:#f59f00,color:#000
    style DONE fill:#cc5de8,stroke:#9c36b5,color:#fff

Pytest Fixture Lifecycle

Fixtures provide test dependencies. They set up before the test and tear down after, with different scopes controlling how often.

sequenceDiagram
    participant Pytest as pytest runner
    participant Fix as @pytest.fixture
    participant Test as test_function

    Note over Pytest: Collect tests<br/>Resolve fixture dependencies

    Pytest->>Fix: Call fixture function
    Note over Fix: SETUP phase<br/>Create database connection<br/>Seed test data

    Fix-->>Pytest: yield db_connection

    Pytest->>Test: test_create_user(db_connection)
    Note over Test: Run assertions<br/>assert user.name == "Alice"
    Test-->>Pytest: Test passed or failed

    Pytest->>Fix: Resume after yield
    Note over Fix: TEARDOWN phase<br/>Delete test data<br/>Close connection

    Note over Pytest: Fixture scopes control reuse:<br/>function = once per test (default)<br/>class = once per test class<br/>module = once per file<br/>session = once per entire run