top of page

Set Up the Test Pyramid — Test Automation in Practice, Part 2

S. West
Jul 8
3 min read

Updated: 4 days ago

Test Automation in Practice · Part 2

Corrected, 4 October 2026. We re-checked this post's figures against their original sources. The 70/20/10 split is now attributed to its source (Google Testing Blog, 2015), and the trophy split is labelled as illustrative.

Most test suites drift into an upside-down pyramid — a few slow, brittle end-to-end tests doing the work a pile of fast unit tests should be doing — so the suite ends up slow and flaky at the same time. The test pyramid is the shape that fixes that: mostly fast unit tests, fewer integration tests, a thin layer of end-to-end. This walks through reshaping an existing suite toward it, level by level.


The test pyramid (or trophy) is the deliberate allocation of testing effort across levels. Without it, teams accumulate tests randomly and end up with slow, fragile suites. This tutorial walks through implementing one.


What You'll Build


A test suite organized by level, with clear allocation and folder structure.


Step 1: Inventory What You Have (15 min)


Look at your existing tests:


find tests -name "*.test.*" -o -name "test_*.py" | head -30

For each, ask: what level is this? Unit, integration, or E2E?


You probably have:


  • A mix

  • No clear organization

  • Some heavily-mocked unit tests that are really integration tests in disguise

  • Slow tests labeled as unit tests


Step 2: Define Your Levels (10 min)


For your codebase:


- Unit: pure logic, no I/O, <100ms each
- Integration: with real database / real services internal to your system, <500ms each
- E2E: full stack via UI or public API, <30s each
- Smoke: subset of E2E for fast critical-path verification, <5 min total

Document these clearly.


Step 3: Reorganize the Folder Structure (30 min)


tests/
  unit/
    test_pricing.py
    test_validators.py
  integration/
    test_user_service.py
    test_order_service.py
  e2e/
    test_signin_flow.spec.ts
    test_checkout_flow.spec.ts
  smoke/
    test_smoke.spec.ts

Move existing tests to their correct level. Some will reveal themselves as miscategorized (e.g., "unit" tests that hit the database).


Step 4: Configure Per-Level Running (15 min)


In package.json or Makefile:


{
  "scripts": {
    "test:unit": "vitest run tests/unit",
    "test:integration": "vitest run tests/integration",
    "test:e2e": "playwright test",
    "test:smoke": "playwright test --grep @smoke",
    "test:all": "npm run test:unit && npm run test:integration && npm run test:e2e"
  }
}

Each level can run independently. Fast feedback for developers.


Step 5: Set Allocation Targets (10 min)


Aim for a shape that fits your context:


Classic pyramid (Google's "good first guess", per its Testing Blog, 2015; the exact mix varies by team):


  • 70% unit, 20% integration, 10% E2E


Trophy (more common for orchestration-heavy services). The trophy model prescribes a shape, not percentages, so this split is illustrative:


  • 5% static, 25% unit, 60% integration, 10% E2E


These are rough targets. Don't optimize the count; optimize the value.


Step 6: CI Allocation (15 min)


Different levels run at different cadences in CI:


jobs:
  unit:
    runs-on: ubuntu-latest
    steps:
      - run: npm run test:unit
  
  integration:
    runs-on: ubuntu-latest
    services:
      postgres: { ... }
    steps:
      - run: npm run test:integration
  
  smoke-e2e:
    runs-on: ubuntu-latest
    steps:
      - run: npm run test:smoke

  full-e2e:
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - run: npm run test:e2e

Unit, integration, and smoke run on every PR. Full E2E runs on merge to main.


Step 7: Speed Targets (10 min)


Level

Target

Unit (full)

<60s

Integration (full)

<5min

Smoke E2E

<5min

Full E2E

<30min


If you exceed targets, optimize. Slow tests get run less often.


Step 8: Diagnose Misallocation (ongoing)


Watch for:


  • Tests labeled "unit" that take seconds — really integration tests

  • Tests labeled "integration" that mock everything — really unit tests

  • Tests labeled "E2E" that don't go through the UI — really integration tests


The label should match the actual level.


What You Just Did


You converted ad-hoc test accumulation into deliberate allocation. The team now has clear categories, fast feedback per category, and a shape they're targeting.


Common Failure Modes


Misclassified tests. Slow unit tests; mocked integration tests. The labels lie.


Static targets. Allocation set once; never adjusted. As the system evolves, the right allocation changes.


Over-emphasis on E2E. Heavy investment in E2E because they "test everything." Result: slow, brittle suites.


Under-emphasis on integration. Skipping the level that catches most real bugs.


Manual running. Tests exist but aren't wired to CI by level.



For the reasoning behind this step-by-step guide, read Test Pyramid vs Test Trophy: Choosing a Model.


To aim your own suite at the places where failure costs something, the Risk-Based Test Strategy Worksheet maps risk, checks that tests assert behaviour, lists the edge cases and finds the boundaries that need contract tests.


Sources



Continue the Test Automation in Practice path



Part of the Test Automation in Practice learning path.

bottom of page