sodcalculator

10 Tech How-To

How to Choose a Software Testing Strategy: The Testing Pyramid

Guide 10-41977 · Updated · Step by step

This guide explains how to use the testing pyramid to choose a software testing strategy that balances speed, cost, and reliability. You’ll learn what each layer of the pyramid covers, how to decide what to test at each level, and how to avoid common pitfalls that lead to brittle or slow test suites.

What You’ll Need

  • A basic understanding of software testing concepts (unit, integration, end-to-end).
  • Knowledge of your project’s tech stack and testing tools (e.g., JUnit, pytest, Selenium).
  • Access to your codebase and test infrastructure (CI/CD pipeline).

Step-by-Step

  1. Understand the pyramid model. The testing pyramid has three layers: unit tests at the base (many, fast, isolated), integration tests in the middle (fewer, involve external dependencies like databases or APIs), and end-to-end (E2E) tests at the top (few, slow, simulate real user flows). The shape implies you should have many more unit tests than E2E tests.
  2. Assess your project’s risk profile. Identify the most critical user journeys and business logic. If a bug there would cause data loss or security issues, those areas need more coverage at all layers. For less critical features, a few unit tests may suffice.
  3. Start with unit tests for core logic. Write unit tests for pure functions, algorithms, and business rules that don’t depend on external systems. Aim for high coverage on the most complex or frequently changed code. These tests run in milliseconds and give fast feedback.
  4. Add integration tests for boundaries. For code that interacts with databases, file systems, or third-party services, write integration tests that use real or test doubles. Test one integration at a time (e.g., repository + database, service + API client). This catches contract mismatches and SQL errors that unit tests miss.
  5. Use E2E tests sparingly. Write end-to-end tests only for the most critical user journeys—like login, checkout, or data export. Keep the number low (typically under 10-20 per application) because they are slow and flaky. Run them in a separate CI stage after unit and integration tests pass.
  6. Match test scope to development stage. During rapid feature development, rely on unit tests for immediate feedback. Before releases, run the full suite including E2E. If you’re working on a legacy system with no tests, start by adding unit tests to new code, then gradually add integration tests around existing modules.
  7. Review and adjust regularly. Every few months, analyze test execution times and failure rates. If E2E tests are flaky, move coverage down to integration tests. If unit tests are too coupled to implementation details, refactor them to focus on behavior. The pyramid is a guideline, not a rigid rule—adjust based on your architecture and team’s capacity.

Common Mistakes

  • Inverted pyramid: Writing too many E2E tests and few unit tests. This makes the suite slow and brittle.
  • Testing implementation details: Unit tests that assert on internal method calls or private state break easily and don’t verify real behavior.
  • Ignoring integration testing: Skipping integration tests leads to bugs that only appear when components interact (e.g., wrong column name in SQL).
  • Using the same test data everywhere: Sharing fixtures across unit, integration, and E2E tests creates hidden dependencies and makes failures hard to trace.
  • Not running tests in CI: If tests only run locally, they don’t protect against regressions when merging code.

Tips

  • Follow the 70-20-10 rule as a starting point: 70% unit, 20% integration, 10% E2E. Adjust based on your project.
  • Use test doubles (mocks, stubs) for unit tests, but prefer real instances for integration tests to catch actual integration issues.
  • Make E2E tests independent and idempotent—they should not depend on the order they run in.
  • Measure coverage per layer separately. For example, track unit test coverage for the service layer, integration coverage for repositories, and E2E coverage for critical paths.
  • Invest in fast feedback: run unit tests on every commit, integration tests on pull requests, and E2E tests on merge to main.

Troubleshooting

  • Tests are too slow: If the suite takes more than 10 minutes, identify slow tests (often E2E or integration with real databases). Move them to a nightly job or parallelize them. Consider using testcontainers for database integration tests to keep them fast.
  • Flaky tests: If tests fail intermittently, check for timeouts, race conditions, or external service dependencies. For E2E, add explicit waits instead of fixed sleeps. For integration tests, ensure the test database is reset between runs.
  • Too many tests to maintain: If the team spends more time fixing tests than writing features, prune tests that don’t catch real bugs. Focus on behavior, not implementation. Use mutation testing to find weak tests.
  • Coverage is high but bugs still ship: High coverage doesn’t guarantee good tests. Review test assertions—are they checking the right thing? Add integration tests for the exact scenarios that failed in production.
  • E2E tests block deployment: If E2E tests are the bottleneck, run them in parallel on multiple agents, or split them by feature area. Consider shifting some E2E scenarios to integration tests.

Frequently Asked Questions

  1. What is the ideal ratio of unit to integration to E2E tests?
    There’s no universal ratio, but a common guideline is 70% unit, 20% integration, and 10% E2E. Adjust based on your application’s complexity and risk areas.
  2. Should I write E2E tests for every user story?
    No. E2E tests should cover only the most critical end-to-end flows that involve multiple systems. For less critical features, rely on unit and integration tests.
  3. How do I test a legacy codebase with no existing tests?
    Start by adding unit tests to new code and refactoring old code to be testable. Then add integration tests for the parts that interact with external systems. Gradually build up the pyramid.
  4. Can I use the testing pyramid for microservices?
    Yes, but each service may have its own pyramid. Additionally, you need contract tests between services to ensure they communicate correctly—these sit between integration and E2E.
  5. What if my team is small and lacks time to write many tests?
    Prioritize tests that protect the most critical business logic and user journeys. Even a small number of well-targeted tests is better than none. Automate what you can and run them in CI.
  6. Is it okay to have more integration tests than unit tests?
    If your application is heavily integrated with external systems (e.g., data pipelines), it might be justified. The pyramid is a guideline; the key is to keep the suite fast and reliable.

Related

General steps for current mainstream versions; menus can differ by device and version. See the disclaimer or report an error.

More in this bed

The whole Tech How-To bed →