Skip to content
SP StackPractices
intermediate

Software Testing Strategy Guide

A practical guide to building a layered testing strategy with unit, integration, and end-to-end tests.

Introduction

Testing is not just about finding bugs. A well-designed testing strategy provides confidence for refactoring, documents expected behavior, catches regressions before they reach production, and serves as executable specifications for your system.

This guide covers the test pyramid, test types, when to use each, and how to integrate testing into your CI/CD pipeline.

The Testing Pyramid

The ideal test suite follows a pyramid shape:

      /\
     /  \    E2E Tests (few, slow, expensive)
    /____\
   /      \  Integration Tests (some, medium speed)
  /        \
 /__________\ Unit Tests (many, fast, cheap)
LayerScopeSpeedCostQuantity
UnitSingle function/classMillisecondsLowMany (70-80%)
IntegrationMultiple componentsSecondsMediumSome (15-25%)
E2EFull user flowsMinutesHighFew (5-10%)

Unit Testing

Unit tests verify individual functions or classes in isolation. They are your first line of defense.

What to Test

  • Business logic and algorithms
  • Edge cases (null, empty, overflow)
  • Error handling paths
  • Boundary conditions

What NOT to Test

  • Framework code (ORM, HTTP layer)
  • Third-party libraries
  • Simple getters/setters without logic

Example (Python with pytest)

def calculate_discount(price: float, customer_type: str) -> float:
    if customer_type == "vip":
        return price * 0.8
    return price * 0.95

# Test
import pytest

@pytest.mark.parametrize("price,customer_type,expected", [
    (100.0, "vip", 80.0),
    (100.0, "regular", 95.0),
    (0.0, "vip", 0.0),
])
def test_calculate_discount(price, customer_type, expected):
    assert calculate_discount(price, customer_type) == expected

Integration Testing

Integration tests verify that multiple components work together correctly.

What to Test

  • Database queries and migrations
  • API endpoint behavior (with real or test DB)
  • Message queue publishing/consuming
  • External service interactions (with test doubles)

Example (Node.js with supertest)

const request = require('supertest');
const app = require('../app');

describe('POST /api/users', () => {
  it('creates a user and returns 201', async () => {
    const res = await request(app)
      .post('/api/users')
      .send({ name: 'Alice', email: 'alice@example.com' });

    expect(res.status).toBe(201);
    expect(res.body.email).toBe('alice@example.com');
  });
});

End-to-End Testing

E2E tests simulate real user behavior through the entire application stack.

What to Test

  • Critical user journeys (login, checkout, signup)
  • Cross-browser compatibility
  • Mobile responsiveness
  • Accessibility compliance

Tools by Stack

StackRecommended Tool
WebPlaywright, Cypress
MobileAppium, Maestro
APIREST Assured, Postman

Test Coverage Goals

  • Line coverage: 70-80% minimum for business logic
  • Branch coverage: Prioritize over line coverage
  • Critical paths: 100% coverage for payment, auth, and security flows

CI/CD Integration

# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run test:unit -- --coverage
      - run: npm run test:integration
      - run: npm run test:e2e

Best Practices

  • Write tests first (TDD) for complex logic or bug fixes
  • Use test data builders instead of hardcoding fixtures
  • Mock external services at integration test boundaries
  • Run tests in parallel to keep feedback loops fast
  • Fail CI on coverage regression, not on arbitrary targets
  • Keep E2E tests deterministic: avoid timing-dependent assertions

Common Anti-Patterns

  • Testing implementation details instead of behavior
  • Sharing mutable state between tests
  • Using sleep() instead of explicit waits in E2E tests
  • Mocking everything in integration tests
  • Ignoring flaky tests instead of fixing root causes

Summary Checklist

  • Unit tests for all business logic functions
  • Integration tests for database and API layers
  • E2E tests for critical user journeys
  • Tests run in CI on every pull request
  • Coverage tracked and reported
  • Flaky tests identified and fixed promptly

Frequently Asked Questions

What is the testing pyramid?

The testing pyramid is a model that suggests having many unit tests at the base, fewer integration tests in the middle, and very few end-to-end tests at the top. This keeps the test suite fast, reliable, and cost-effective.

How much test coverage should I aim for?

Aim for 70-80% coverage on critical business logic. Higher coverage is better, but 100% coverage does not guarantee correctness. Focus on behavior and edge cases rather than hitting arbitrary percentages.

Should I mock external APIs in integration tests?

Mock external APIs at integration test boundaries using libraries like WireMock or MSW. This keeps tests deterministic and fast while still verifying your system’s interaction patterns.