← Back to blog

Testing Strategy in Practice: From Unit Tests to Integration Tests

Testing is not about "testing is better than not testing" — it is about "what to test, how to test, and how much to test." This article covers the test pyramid, responsibilities of unit tests, integration tests, and E2E tests, coverage strategies, and how to make testing part of the development workflow — for teams building or optimizing their testing practice.

The Bottom Line: Testing Is Not About “Did We Test” — It Is About “Did We Test the Right Things”

Many teams treat testing as a “pre-deployment security check,” cutting test time to meet deadlines, or blindly chasing coverage metrics. Both approaches miss the point — testing is part of the development workflow, not a gate.

This article covers the test pyramid and practical approaches for each layer.


1. The Test Pyramid

       /\
      /  \         E2E tests (few, critical paths)
     /    \
    /------\
   /Integration\   (key path coverage)
  /------------\
 /  Unit Tests  \   (many, fast, core logic)
/----------------\

Layer Responsibilities

LayerSpeedQuantityResponsibility
Unit testsMillisecondsManyVerify individual function/class behavior
Integration testsSecondsMediumVerify inter-module interaction
E2E testsMinutesFewVerify critical user flows

2. Unit Tests

What to Test

  • Business logic (calculations, validation, transformations)
  • Edge cases (nulls, out-of-bounds, invalid input)
  • Error handling (does the function return the correct error?)

What NOT to Test

  • Framework behavior (Express routing, React rendering — already tested by the framework)
  • Database queries (that is integration testing)
  • Third-party API calls (mock them, only verify call parameters and response handling)

Example

function calculateDiscount(amount: number, level: 'bronze' | 'silver' | 'gold'): number {
  const rates = { bronze: 0, silver: 0.1, gold: 0.2 };
  return amount * (1 - rates[level]);
}

describe('calculateDiscount', () => {
  it('returns full amount for bronze', () => {
    expect(calculateDiscount(100, 'bronze')).toBe(100);
  });
  it('applies 10% discount for silver', () => {
    expect(calculateDiscount(100, 'silver')).toBe(90);
  });
  it('handles zero amount', () => {
    expect(calculateDiscount(0, 'gold')).toBe(0);
  });
});

3. Integration Tests

What to Test

  • API request and response handling
  • Database read/write operations
  • Service-to-service message passing

Configuration

beforeAll(async () => {
  await db.migrate.latest();
  await db.seed.run();
});

afterAll(async () => {
  await db.destroy();
});

describe('POST /users', () => {
  it('creates a new user', async () => {
    const res = await request(app)
      .post('/users')
      .send({ name: 'test', email: 'test@test.com' });
    expect(res.status).toBe(201);
    expect(res.body.name).toBe('test');
  });
});

4. E2E Tests

What to Test

  • Critical user flows (registration, login, checkout)
  • Cross-system interactions (frontend → API → database → third-party)

Principles

  • Test only critical paths, not all paths
  • Use Cypress or Playwright
  • Run daily in CI, or before merging to main

5. Testing Strategy Summary

Test TypeCoverage TargetFrequencyFail Allowed?
Unit testsCore logic 80%+Every commitNo
Integration testsKey API pathsDailyNo
E2E testsCritical user flowsBefore mergeYes (requires manual review)

Testing maturity is not measured by “how many tests you have” — it is measured by “how naturally testing is integrated into your development workflow.” The ideal state: writing tests feels as natural as writing code, and not writing tests feels wrong.

Need testing system design or technical consulting? Contact us — tell us about your project scale and tech stack, feasibility within 24 hours.

FAQ

What code coverage should we aim for?

Do not chase 100% coverage. Core logic (business rules, data processing, API endpoints) should target 80%+ coverage; UI layer and glue code can be at 50%. The key is not the coverage number but whether critical paths are tested. A project with 60% coverage but all core logic tested is better than 90% coverage with core logic untested.

Where is the line between unit tests and integration tests?

Unit tests do not depend on external systems (database, API, file system) — all external dependencies are mocked. Integration tests verify inter-module interactions with real external systems (test database, test API). The dividing line: if your test needs to start a database, it is an integration test, not a unit test.

What if tests are too slow?

Run them in layers: unit tests on every commit (seconds), integration tests daily (minutes), E2E tests before merging to main (tens of minutes). Slow tests are not eliminated — they are just not blocking the development flow.

Is TDD actually useful?

Yes, but it requires practice. The core value of TDD is not "write tests before code" — it is "think about expected behavior before writing code." If you are new to TDD, start with bug fixes: write a test that reproduces the bug, then fix the code, then the test passes. This gives you confidence and ensures the bug never returns.

This article comes from AI Enable Harness front-line delivery practice. Need a similar system or optimization service?

📡 Also published on: CSDN 知乎

Subscribe to Updates

Get notified when new articles are published. No spam, occasional updates only.

Subscribe →