Alembicalembic
← Back to Skills

Testing Strategy Guide

stable

A comprehensive approach to testing software, covering unit tests, integration tests, and end-to-end tests with practical patterns.

Edit on GitHub
Version1.0.0
Last Reviewed2026-01-08
Compatible Withcopilot, cursor, claude, codex
testingjavascripttypescriptpython

Testing Strategy Guide

A comprehensive approach to testing software at every level of the testing pyramid.

Purpose

Good tests give you confidence to ship fast. Bad tests slow you down and give false confidence. This skill helps you write the right tests at the right level, maximizing value while minimizing maintenance burden.

Prerequisites

  • Testing framework installed (Jest, Vitest, pytest, etc.)

  • Understanding of the code to be tested

  • Access to run tests locally
  • Steps

    Step 1: Identify What to Test

    Use the testing pyramid as a guide:

            /\
    / \ E2E (few, slow, high confidence)
    /----\
    / \ Integration (some, medium speed)
    /--------\
    / \ Unit (many, fast, isolated)
    --------------

    Test Priority:


    1. Critical user paths (auth, checkout, data operations)
    2. Complex business logic
    3. Edge cases and error handling
    4. Recently fixed bugs (regression tests)

    Expected outcome: Clear list of what needs testing.

    Step 2: Write Unit Tests

    Test individual functions in isolation.

    // Example: Testing a utility function
    describe('calculateDiscount', () => {
    it('applies percentage discount correctly', () => {
    expect(calculateDiscount(100, 10)).toBe(90);
    });

    it('handles zero discount', () => {
    expect(calculateDiscount(100, 0)).toBe(100);
    });

    it('throws on negative discount', () => {
    expect(() => calculateDiscount(100, -10)).toThrow();
    });

    it('handles decimal precision', () => {
    expect(calculateDiscount(100, 33.33)).toBeCloseTo(66.67);
    });
    });

    Expected outcome: Fast, isolated tests for logic.

    Step 3: Write Integration Tests

    Test components working together.

    // Example: Testing API route with database
    describe('POST /api/users', () => {
    beforeEach(async () => {
    await db.reset();
    });

    it('creates user and returns 201', async () => {
    const response = await request(app)
    .post('/api/users')
    .send({ email: 'test@example.com', name: 'Test' });

    expect(response.status).toBe(201);
    expect(response.body.id).toBeDefined();

    // Verify persistence
    const user = await db.users.findById(response.body.id);
    expect(user.email).toBe('test@example.com');
    });

    it('returns 400 on invalid email', async () => {
    const response = await request(app)
    .post('/api/users')
    .send({ email: 'invalid', name: 'Test' });

    expect(response.status).toBe(400);
    });
    });

    Expected outcome: Confidence that parts work together.

    Step 4: Write E2E Tests (Sparingly)

    Test critical user flows end-to-end.

    // Example: Playwright E2E test
    test('user can sign up and make purchase', async ({ page }) => {
    // Sign up
    await page.goto('/signup');
    await page.fill('[name="email"]', 'new@example.com');
    await page.fill('[name="password"]', 'SecurePass123!');
    await page.click('button[type="submit"]');
    await expect(page.locator('text=Welcome')).toBeVisible();

    // Make purchase
    await page.goto('/products');
    await page.click('text=Add to Cart');
    await page.click('text=Checkout');
    await page.fill('[name="card"]', '4242424242424242');
    await page.click('text=Pay');
    await expect(page.locator('text=Order confirmed')).toBeVisible();
    });

    Expected outcome: Critical paths verified end-to-end.

    Checks

    Automated Checks

    Run all tests


    npm test

    Run with coverage


    npm test -- --coverage

    Verify coverage thresholds


    npm test -- --coverage --coverageThreshold='{"global":{"branches":80}}'

    Manual Checks

  • Tests run in under 30 seconds (unit)

  • No flaky tests (run 3x to verify)

  • Test names describe the behavior

  • Tests are independent (order doesn't matter)
  • Failure Modes

    SymptomCauseResolution

    Tests are slowToo many E2E testsMove logic to unit tests
    Tests are flakyTiming dependenciesAdd proper waits, mock time
    Tests break on unrelated changesTests too coupledUse better abstractions
    100% coverage but bugs still shipTesting implementation, not behaviorFocus on outcomes

    Rollback

    If tests become problematic:

    Skip flaky test temporarily (fix soon!)


    it.skip('flaky test', () => {});

    Mark test as todo


    it.todo('test to implement later');

    Variations

    TDD Flow

    1. Write failing test
    2. Write minimum code to pass
    3. Refactor
    4. Repeat

    Testing Legacy Code

    1. Write characterization tests (capture current behavior)
    2. Refactor with safety net
    3. Replace characterization tests with proper unit tests

    Related Skills

  • [Code Review](../code-review/SKILL.md) - Reviewing test quality

  • [Git Workflow](../git-workflow/SKILL.md) - Committing with tests
  • References

  • [Testing Trophy](https://kentcdodds.com/blog/the-testing-trophy-and-testing-classifications)

  • [Jest Best Practices](https://github.com/goldbergyoni/javascript-testing-best-practices)