Test Strategy Best Practices | Testing Pyramid Guide
Build test strategy with the testing pyramid: 70% unit, 20% integration, 10% E2E. GitScrum tracks testing tasks alongside development work.
10 min read
A solid test strategy catches bugs early, enables confident refactoring, and prevents regressions from reaching production. The testing pyramidβunit tests at the base, integration in the middle, E2E at the topβprovides the right balance of speed and coverage. GitScrum helps teams track testing tasks alongside development work, ensuring quality is built into the process, not bolted on at the end.
Test Types
| Type | Speed | Scope | Count |
|---|---|---|---|
| Unit | Fast | Single function/class | Many |
| Integration | Medium | Component connections | Some |
| E2E | Slow | Full user flows | Few |
| Contract | Fast | API boundaries | As needed |
| Performance | Slow | Load/stress | Key paths |
Testing Pyramid
TEST PYRAMID STRUCTURE
PYRAMID SHAPE:
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β βββββββββββ β
β β E2E β Few, slow, critical β
β ββββ΄ββββββββββ΄βββ β
β β Integration β Some, moderate β
β ββββ΄ββββββββββββββββ΄βββ β
β β Unit β Many, fast β
β βββββββββββββββββββββββ β
β β
β Good balance: β
β βββ 70% unit tests β
β βββ 20% integration tests β
β βββ 10% E2E tests β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
ANTI-PATTERN - ICE CREAM CONE:
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β βββββββββββββββββββββββ β
β β Manual testing β β
β βββββββββ¬ββββββββββββββ β
β ββββββ΄βββββ β
β β E2E β Too many E2E β
β ββββββ¬βββββ β
β βββββββ΄ββββββ β
β βIntegrationβ β
β βββββββ¬ββββββ β
β βββββ΄ββββ β
β β Unit β Too few unit β
β βββββββββ β
β β
β Problems: Slow CI, flaky tests, long feedback β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
Unit Testing
UNIT TEST STRATEGY
WHAT TO UNIT TEST:
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β High priority: β
β βββ Business logic β
β βββ Complex calculations β
β βββ State transitions β
β βββ Edge cases β
β βββ Error handling β
β βββ Utility functions β
β β
β Low priority: β
β βββ Simple getters/setters β
β βββ Framework boilerplate β
β βββ Third-party library wrappers β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
UNIT TEST PRINCIPLES:
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β FIRST: β
β βββ Fast: Milliseconds, not seconds β
β βββ Isolated: No external dependencies β
β βββ Repeatable: Same result every time β
β βββ Self-validating: Pass/fail clear β
β βββ Timely: Written with or before code β
β β
β AAA Pattern: β
β βββ Arrange: Set up test data β
β βββ Act: Execute the function β
β βββ Assert: Verify the result β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
GOOD UNIT TEST EXAMPLE:
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β describe('calculateDiscount', () => { β
β it('applies 10% discount for orders > $100', β
β () => { β
β // Arrange β
β const order = { total: 150 }; β
β β
β // Act β
β const result = calculateDiscount(order); β
β β
β // Assert β
β expect(result).toBe(15); β
β }); β
β β
β it('returns 0 for orders <= $100', () => { β
β const order = { total: 100 }; β
β expect(calculateDiscount(order)).toBe(0); β
β }); β
β }); β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
Integration Testing
INTEGRATION TEST STRATEGY
WHAT TO INTEGRATION TEST:
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β βββ Database queries and transactions β
β βββ API endpoints β
β βββ Service-to-service communication β
β βββ Message queue producers/consumers β
β βββ External API integrations β
β βββ Cache interactions β
β β
β Focus on: β
β βββ Happy path works end-to-end β
β βββ Error handling at boundaries β
β βββ Data flows correctly between components β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
INTEGRATION TEST APPROACH:
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β Database tests: β
β βββ Use test database (not mock) β
β βββ Reset data between tests β
β βββ Use transactions for cleanup β
β βββ Consider testcontainers β
β β
β API tests: β
β βββ Test real HTTP calls β
β βββ Verify response format β
β βββ Check status codes β
β βββ Test auth/authorization β
β β
β External services: β
β βββ Use mocks for third-party APIs β
β βββ Or use sandbox environments β
β βββ Test contract separately β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
API TEST EXAMPLE:
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β describe('POST /api/orders', () => { β
β it('creates order and returns 201', async () β
β => { β
β const response = await request(app) β
β .post('/api/orders') β
β .send({ product: 'widget', qty: 2 }) β
β .set('Authorization', 'Bearer token'); β
β β
β expect(response.status).toBe(201); β
β expect(response.body.id).toBeDefined(); β
β β
β // Verify in database β
β const order = await db.orders β
β .findById(response.body.id); β
β expect(order.product).toBe('widget'); β
β }); β
β }); β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
E2E Testing
E2E TEST STRATEGY
WHAT TO E2E TEST:
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β Test critical user journeys: β
β βββ Sign up and onboarding β
β βββ Core feature happy paths β
β βββ Purchase/checkout flow β
β βββ Authentication flow β
β βββ Critical error recovery β
β β
β Limit scope: β
β βββ 10-20 critical scenarios β
β βββ Not every permutation β
β βββ Cover what other tests can't β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
E2E TEST PRINCIPLES:
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β βββ Test user behavior, not implementation β
β βββ Use stable selectors (data-testid) β
β βββ Handle async properly (wait for elements) β
β βββ Keep tests independent β
β βββ Clean up test data β
β βββ Run in isolated environment β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
FIGHTING FLAKINESS:
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β Common causes and fixes: β
β β
β Timing issues: β
β βββ Use explicit waits for elements β
β βββ Wait for network requests to complete β
β βββ Don't use arbitrary sleeps β
β β
β Data issues: β
β βββ Create fresh test data each run β
β βββ Clean up after tests β
β βββ Use unique identifiers β
β β
β Environment issues: β
β βββ Consistent test environment β
β βββ Control external dependencies β
β βββ Retry flaky tests with investigation β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
CI/CD Integration
TESTS IN CI/CD PIPELINE
PIPELINE STRATEGY:
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β On every commit (< 5 min): β
β βββ Linting β
β βββ Type checking β
β βββ Unit tests β
β β
β On PR (< 15 min): β
β βββ Unit tests β
β βββ Integration tests β
β βββ Build verification β
β β
β Pre-deploy (< 30 min): β
β βββ Full test suite β
β βββ E2E tests β
β β
β Nightly/scheduled: β
β βββ Full E2E suite β
β βββ Performance tests β
β βββ Security scans β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
OPTIMIZING CI TIME:
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β Parallelization: β
β βββ Run test suites in parallel β
β βββ Shard large test suites β
β βββ Use multiple CI runners β
β β
β Smart selection: β
β βββ Run only affected tests on commits β
β βββ Full suite on main branch β
β βββ Use dependency graph for selection β
β β
β Caching: β
β βββ Cache node_modules β
β βββ Cache Docker layers β
β βββ Cache test database setup β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
Coverage Strategy
TEST COVERAGE APPROACH
COVERAGE TARGETS:
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β Overall target: 70-80% β
β β
β Differentiated targets: β
β βββ Core business logic: 90%+ β
β βββ API handlers: 80% β
β βββ Utilities: 70% β
β βββ UI components: 60-70% β
β β
β Exclude from coverage: β
β βββ Generated code β
β βββ Configuration files β
β βββ Type definitions β
β βββ Third-party wrappers β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
MEANINGFUL COVERAGE:
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β β Test behavior, not lines β
β β Cover edge cases and error paths β
β β Test critical paths thoroughly β
β β Test code that has caused bugs β
β β
β β Just hit lines for metrics β
β β Test implementation details β
β β Ignore error handling β
β β Skip complex code because it's hard β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
COVERAGE ENFORCEMENT:
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β In CI: β
β βββ Fail if coverage drops below threshold β
β βββ Report coverage on PRs β
β βββ Track coverage trends over time β
β β
β In code review: β
β βββ New code must include tests β
β βββ Check tests are meaningful β
β βββ Coverage for critical changes β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
Maintenance
TEST MAINTENANCE
AVOIDING TEST DEBT:
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β Write maintainable tests: β
β βββ Clear test names that describe behavior β
β βββ DRY with test utilities β
β βββ Avoid testing implementation details β
β βββ Use page objects for E2E β
β βββ Keep tests simple and readable β
β β
β Test naming: β
β β "should return empty array for no matches" β
β β "test1" or "returns correctly" β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
DEALING WITH FLAKY TESTS:
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β 1. Quarantine: Move to separate suite β
β 2. Investigate: Find root cause β
β 3. Fix or remove: Don't leave broken β
β 4. Track: Monitor flakiness rates β
β β
β Flaky test policy: β
β βββ Fails 2x in a week β investigate β
β βββ Can't fix in 2 days β quarantine β
β βββ In quarantine > 1 week β delete β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
TEST REFACTORING:
βββββββββββββββββββββββββββββββββββββββββββββββββββ
β When to refactor tests: β
β βββ Tests are slow (minutes for unit) β
β βββ Tests are brittle (break on refactor) β
β βββ Tests are hard to understand β
β βββ Duplicated test setup β
β βββ Can't tell what failed from test name β
βββββββββββββββββββββββββββββββββββββββββββββββββββ
Best Practices
Anti-Patterns
β Ice cream cone (too many E2E)
β Testing implementation details
β Ignoring flaky tests
β Chasing coverage numbers only
β No tests in CI
β Tests slower than development