Factor #12: Depth - Coverage Tells You What Ran, Not What Works

100% code coverage with 0% bug detection is possible and common. Coverage is a flashlight in a dark roomβ€”it shows where you've looked, not what you've found.

When Coverage Lies

100% Coverage, 0% Effectiveness
// Function to test
function calculateDiscount(price, customerType) {
  if (customerType === 'premium') {
    return price * 0.8; // 20% off
  } else if (customerType === 'regular') {
    return price * 0.9; // 10% off
  }
  return price; // BUG: Should be price * 1.0 for clarity
}

// 😱 BAD: 100% coverage but catches nothing
test('discount calculation', () => {
  // Covers all branches but doesn't verify correctness!
  calculateDiscount(100, 'premium');
  calculateDiscount(100, 'regular');
  calculateDiscount(100, 'new');
  
  // No assertions! Still gets 100% coverage!
});

// Still 100% coverage, still useless
test('discount calculation with weak assertions', () => {
  const premium = calculateDiscount(100, 'premium');
  const regular = calculateDiscount(100, 'regular');
  const newCustomer = calculateDiscount(100, 'new');
  
  expect(premium).toBeDefined();
  expect(regular).toBeDefined();
  expect(newCustomer).toBeDefined();
  // Verifies nothing about correctness!
});

// βœ… GOOD: Lower coverage, actually catches bugs
test('premium customers get 20% discount', () => {
  expect(calculateDiscount(100, 'premium')).toBe(80);
  expect(calculateDiscount(50, 'premium')).toBe(40);
});

test('regular customers get 10% discount', () => {
  expect(calculateDiscount(100, 'regular')).toBe(90);
  expect(calculateDiscount(200, 'regular')).toBe(180);
});

test('unknown customers pay full price', () => {
  expect(calculateDiscount(100, 'new')).toBe(100);
  expect(calculateDiscount(100, null)).toBe(100);
  expect(calculateDiscount(100, 'invalid')).toBe(100);
});

Beyond Coverage: Mutation Testing

Mutation Testing: Do Your Tests Actually Work?
// Original function
function isAdult(age) {
  return age >= 18;
}

// Mutation testing introduces bugs (mutants):
// Mutant 1: return age > 18;  (boundary change)
// Mutant 2: return age >= 17; (constant change)
// Mutant 3: return age <= 18; (operator change)
// Mutant 4: return true;      (always true)

// 😱 BAD: Weak tests that don't kill mutants
test('age check', () => {
  expect(isAdult(25)).toBe(true);
  expect(isAdult(10)).toBe(false);
  // Doesn't test boundary! Mutants 1 & 2 survive!
});

// βœ… GOOD: Tests that kill all mutants
test('adult age boundary', () => {
  expect(isAdult(17)).toBe(false); // Kills mutant 2
  expect(isAdult(18)).toBe(true);  // Kills mutant 1
  expect(isAdult(19)).toBe(true);  // Confirms boundary
});

test('handles edge cases', () => {
  expect(isAdult(0)).toBe(false);   // Kills mutant 3
  expect(isAdult(-1)).toBe(false);  // Kills mutant 4
  expect(isAdult(100)).toBe(true);  // Verifies upper range
});

// Mutation testing tools:
// JavaScript: Stryker
// Java: PIT
// Python: mutmut
// C#: Stryker.NET

Property-Based Testing

Test Properties, Not Examples
// 😱 BAD: Testing specific examples
test('sorting works', () => {
  expect(sort([3, 1, 2])).toEqual([1, 2, 3]);
  expect(sort([5, 2, 8, 1])).toEqual([1, 2, 5, 8]);
  // What about edge cases we didn't think of?
});

// βœ… GOOD: Property-based testing
import fc from 'fast-check';

test('sorting properties', () => {
  fc.assert(
    fc.property(fc.array(fc.integer()), (arr) => {
      const sorted = sort(arr);
      
      // Property 1: Same length
      expect(sorted.length).toBe(arr.length);
      
      // Property 2: Is ordered
      for (let i = 1; i < sorted.length; i++) {
        expect(sorted[i]).toBeGreaterThanOrEqual(sorted[i-1]);
      }
      
      // Property 3: Same elements (permutation)
      const origCounts = countElements(arr);
      const sortedCounts = countElements(sorted);
      expect(sortedCounts).toEqual(origCounts);
    })
  );
});

// Tests with 100s of random inputs automatically!

The Testing Pyramid (Still Matters)

Right Test at the Right Level
// Unit Test (Fast, Focused)
test('password validation', () => {
  expect(isValidPassword('abc')).toBe(false); // Too short
  expect(isValidPassword('abcdefgh')).toBe(false); // No numbers
  expect(isValidPassword('abcd1234')).toBe(false); // No special chars
  expect(isValidPassword('Abc123!@')).toBe(true);
});

// Integration Test (Component Boundaries)
test('user registration API', async () => {
  const response = await request(app)
    .post('/api/register')
    .send({ email: 'test@example.com', password: 'Abc123!@' });
  
  expect(response.status).toBe(201);
  expect(response.body.userId).toBeDefined();
  
  // Verify user was actually created
  const user = await db.users.findByEmail('test@example.com');
  expect(user).toBeDefined();
});

// E2E Test (Critical Paths Only)
test('complete purchase flow', async ({ page }) => {
  await page.goto('/shop');
  await page.click('[data-product="widget"]');
  await page.click('button:has-text("Add to Cart")');
  await page.click('button:has-text("Checkout")');
  await page.fill('#email', 'customer@example.com');
  await page.fill('#card', '4242424242424242');
  await page.click('button:has-text("Purchase")');
  
  await expect(page).toHaveURL(//confirmation/);
  await expect(page.locator('h1')).toContainText('Thank You');
});

Measuring True Test Quality

1. Mutation Score > Coverage %
What percentage of injected bugs do your tests catch?

A 70% mutation score is better than 100% code coverage. It means your tests actually detect 70% of possible bugs.

2. Defect Detection Rate
Track bugs that escaped to production

For each production bug, ask: "Could a test have caught this?" If yes, write that test. Track the ratio over time.

3. Test Failure Relevance
When tests fail, is it for good reasons?
Good failures: Actual bugs, breaking changes
Bad failures: Flaky tests, brittle assertions
Track: Real Bugs Found / Total Test Failures
4. Time to Diagnose
How long to understand why a test failed?

Good tests fail with clear messages. If developers spend more than 2 minutes understanding a failure, the test needs better diagnostics.

Coverage Done Right