Explorer
KNOW-PAT-226

Testing Strategy — Pyramid de tests, TDD, coverage, outils par stack

Domaine
testing
Type
pattern
Priorité
P1

Testing Strategy

Problème

Les projets ship sans tests ou avec des tests déséquilibrés (trop d'E2E, pas d'unitaires), ce qui rend les regressions fréquentes et les déploiements lents.

Solution

Pyramide de tests + TDD sur la logique métier + coverage pragmatique.

1. Pyramide de tests

        /\
       /E2E\        ~10% — flows critiques uniquement, lent
      /------\
     /Integration\  ~30% — API + DB, services réels
    /------------\
   /  Unit Tests  \ ~60% — logique pure, rapide
  /----------------\

2. Outils par stack

Stack Unit Integration E2E
Node.js/TS Vitest, Jest Supertest, Testcontainers Playwright, Cypress
Python pytest, unittest pytest-django, httpx Playwright
PHP Pest, PHPUnit Laravel Dusk Playwright
Go testing, testify dockertest Playwright

3. TDD — quand et comment

// Cycle : Red → Green → Refactor
// 1. Red : écrire un test qui échoue
describe('calculatePrice', () => {
  it('applies discount for premium members', () => {
    const result = calculatePrice(100, { tier: 'premium' });
    expect(result).toBe(80); // 20% discount
  });
});

// 2. Green : écrire le code minimum qui passe
function calculatePrice(base: number, opts: { tier: string }): number {
  if (opts.tier === 'premium') return base * 0.8;
  return base;
}

// 3. Refactor : améliorer sans casser

TDD obligatoire sur : logique métier, calculs financiers, algorithmes, parsers TDD optionnel sur : CRUD simple, boilerplate, configuration

4. Coverage pragmatique

  • 80%+ sur la logique métier (services, domain)
  • 60%+ sur l'API (routes, controllers)
  • Pas de seuil sur l'UI (les tests E2E couvrent les flows)
  • 100% sur les utils critiques (crypto, validation, formatting)
// vitest.config.ts — coverage config
{
  "coverage": {
    "thresholds": {
      "branches": 80,
      "functions": 80,
      "lines": 80,
      "statements": 80
    },
    "exclude": ["**/*.config.*", "**/*.d.ts", "dist/**"]
  }
}

5. Integration testing avec DB

// Testcontainers — DB éphémère par test
import { PostgreSqlContainer } from '@testcontainers/postgresql';

const pg = await new PostgreSqlContainer().start();
const db = drizzle(pg.getConnectionUri());

// Test réel contre DB isolée
const user = await db.insert(users).values({ email: 'test@test.com' }).returning();
expect(user[0].id).toBeDefined();

await pg.stop(); // cleanup automatique

6. E2E — Playwright

// playwright.config.ts
export default defineConfig({
  testDir: './e2e',
  use: { baseURL: 'http://localhost:3000' },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'mobile', use: { ...devices['iPhone 15'] } },
  ],
});

// e2e/checkout.spec.ts
test('checkout flow', async ({ page }) => {
  await page.goto('/products');
  await page.click('[data-testid="add-to-cart"]');
  await page.goto('/checkout');
  await page.fill('[name="email"]', 'test@test.com');
  await page.click('[data-testid="pay"]');
  await expect(page).toHaveURL(/.*success/);
});

Anti-patterns

  • Tests qui dépendent de l'ordre d'exécution
  • Mock de tout (test de mock, pas de code)
  • Pas de test sur la logique financière
  • E2E pour tester une fonction pure

Références

  • [[KNOW-PAT-220]] — Secure SDLC Checklist (phase testing)
  • [[KNOW-REF-043]] — OWASP ASVS (exigences de test)