Skip to content

Testing Pyramid

Alex Stojcic edited this page Apr 7, 2025 · 1 revision

The Testing Pyramid

The Testing Pyramid is a conceptual framework that helps teams understand the ideal distribution of test types in an automated test suite. It was originally proposed by Mike Cohn in his book "Succeeding with Agile".

Visual Representation

    /\
   /  \      UI/E2E Tests (Few)
  /----\     
 /      \    Integration Tests (More)
/--------\
\        /    Unit Tests (Most)
 \______/

Key Principles

The testing pyramid suggests that you should have:

  1. Many unit tests at the base of the pyramid
  2. Some integration tests in the middle
  3. Few end-to-end tests at the top

This distribution optimizes for:

  • Speed: Unit tests are faster to execute than integration or E2E tests
  • Stability: Lower-level tests are less prone to flakiness
  • Cost: Maintaining large numbers of E2E tests is expensive
  • Coverage: The combination provides comprehensive coverage

The Layers Explained

Unit Tests (Base Layer)

Unit tests verify that individual units of code (functions, methods, classes) work correctly in isolation.

Characteristics:

  • Fast: Execute in milliseconds
  • Isolated: No external dependencies (databases, APIs, etc.)
  • Numerous: Should comprise around 70-80% of your test suite
  • Focused: Test a single unit of functionality

Examples:

// Example Jest unit test
test('calculateTotal adds tax correctly', () => {
  expect(calculateTotal(100, 0.1)).toBe(110);
});

Integration Tests (Middle Layer)

Integration tests verify that different units of code work together correctly.

Characteristics:

  • Moderate speed: Execute in seconds
  • Some dependencies: Test interactions between components
  • Moderate number: Should comprise around 15-20% of your test suite
  • Broader scope: Test multiple units working together

Examples:

// Example integration test
test('user service saves to database correctly', async () => {
  const user = { name: 'Test User', email: 'test@example.com' };
  await userService.create(user);
  const savedUser = await db.findUserByEmail('test@example.com');
  expect(savedUser.name).toBe('Test User');
});

UI/E2E Tests (Top Layer)

End-to-end tests verify the entire system works correctly from a user's perspective.

Characteristics:

  • Slower: Execute in seconds or minutes
  • Full dependencies: Test the entire system
  • Fewer tests: Should comprise around 5-10% of your test suite
  • User-focused: Test user journeys and flows

Examples:

// Example Cypress E2E test
it('user can log in and view dashboard', () => {
  cy.visit('/login');
  cy.get('#email').type('user@example.com');
  cy.get('#password').type('password123');
  cy.get('#login-button').click();
  cy.url().should('include', '/dashboard');
  cy.get('h1').should('contain', 'Welcome');
});

Common Anti-Patterns

The Ice Cream Cone Anti-Pattern

When the pyramid is inverted (many E2E tests, few unit tests), it's called an "ice cream cone":

    ____
   /    \    
  /      \   Unit Tests (Few)
 /--------\
/          \  Integration Tests (Some)
\__________/  E2E Tests (Many)

This leads to:

  • Slow test suites
  • Brittle tests
  • High maintenance costs
  • Difficulty in identifying root causes of failures

The Hourglass Anti-Pattern

When there are many unit tests and E2E tests but few integration tests:

    /\
   /  \      E2E Tests (Many)
  /____\     
  \    /     Integration Tests (Few)
   \  /
    \/       Unit Tests (Many)

This leads to:

  • An integration gap
  • Tests that pass in isolation but fail in real usage
  • Missing critical component interaction testing

Implementing the Testing Pyramid

Practical Guidelines

  1. Start with unit tests: Build a solid foundation
  2. Add integration tests: Focus on critical component interactions
  3. Finish with E2E tests: Cover key user journeys
  4. Monitor test distribution: Regularly check your test distribution
  5. Balance test types: Adjust based on project needs and stability

Considerations for Different Applications

Different applications may require different distributions:

  • Frontend-heavy applications: May need more component tests
  • API/backend services: May need more integration tests
  • Critical systems: May warrant more E2E coverage in critical paths
  • Microservices: May need more contract tests between services

Tools for Each Layer

Unit Testing

  • JavaScript: Jest, Mocha, Jasmine
  • Python: pytest, unittest
  • Java: JUnit, TestNG
  • C#: NUnit, xUnit.net

Integration Testing

  • API: Postman, REST Assured, Supertest
  • Database: TestContainers, DBUnit
  • Component: React Testing Library, Vue Test Utils

E2E Testing

  • Web: Cypress, Playwright, Selenium
  • Mobile: Appium, Detox, Espresso
  • API: Postman, Karate DSL

References

Related Wiki Pages