Skill Tracer
Skills › Skills For Real Engineers › tdd › tests.md

tests.md

77 lines · skills/engineering/tdd/tests.md at 4ae2317

Open on GitHub
1# Good and Bad Tests
2

Good and Bad Tests

Raw lines 1 to 2
3## Good Tests
4
5**Integration-style**: Test through real interfaces, not mocks of internal parts.
6
7```typescript
8// GOOD: Tests observable behavior
9test("user can checkout with valid cart", async () => {
10 const cart = createCart();
11 cart.add(product);
12 const result = await checkout(cart, paymentMethod);
13 expect(result.status).toBe("confirmed");
14});
15```
17Characteristics:
19- Tests behavior users/callers care about
20- Uses public API only
21- Survives internal refactors
22- Describes WHAT, not HOW
23- One logical assertion per test

Good Tests

Integration-style: Test through real interfaces, not mocks of internal parts.

// GOOD: Tests observable behavior
test("user can checkout with valid cart", async () => {
  const cart = createCart();
  cart.add(product);
  const result = await checkout(cart, paymentMethod);
  expect(result.status).toBe("confirmed");
});

Characteristics:

  • Tests behavior users/callers care about
  • Uses public API only
  • Survives internal refactors
  • Describes WHAT, not HOW
  • One logical assertion per test
Raw lines 3 to 24
25## Bad Tests
27**Implementation-detail tests**: Coupled to internal structure.
29```typescript
30// BAD: Tests implementation details
31test("checkout calls paymentService.process", async () => {
32 const mockPayment = jest.mock(paymentService);
33 await checkout(cart, payment);
34 expect(mockPayment.process).toHaveBeenCalledWith(cart.total);
35});
36```
38Red flags:
40- Mocking internal collaborators
41- Testing private methods
42- Asserting on call counts/order
43- Test breaks when refactoring without behavior change
44- Test name describes HOW not WHAT
45- Verifying through external means instead of interface
47```typescript
48// BAD: Bypasses interface to verify
49test("createUser saves to database", async () => {
50 await createUser({ name: "Alice" });
51 const row = await db.query("SELECT * FROM users WHERE name = ?", ["Alice"]);
52 expect(row).toBeDefined();
53});
55// GOOD: Verifies through interface
56test("createUser makes user retrievable", async () => {
57 const user = await createUser({ name: "Alice" });
58 const retrieved = await getUser(user.id);
59 expect(retrieved.name).toBe("Alice");
60});
61```
63**Tautological tests**: Expected value restates the implementation, so the test passes by construction.
65```typescript
66// BAD: Expected value is recomputed the way the code computes it
67test("calculateTotal sums line items", () => {
68 const items = [{ price: 10 }, { price: 5 }];
69 const expected = items.reduce((sum, i) => sum + i.price, 0);
70 expect(calculateTotal(items)).toBe(expected);
71});
73// GOOD: Expected value is an independent, known literal
74test("calculateTotal sums line items", () => {
75 expect(calculateTotal([{ price: 10 }, { price: 5 }])).toBe(15);
76});
77```

Bad Tests

Implementation-detail tests: Coupled to internal structure.

// BAD: Tests implementation details
test("checkout calls paymentService.process", async () => {
  const mockPayment = jest.mock(paymentService);
  await checkout(cart, payment);
  expect(mockPayment.process).toHaveBeenCalledWith(cart.total);
});

Red flags:

  • Mocking internal collaborators
  • Testing private methods
  • Asserting on call counts/order
  • Test breaks when refactoring without behavior change
  • Test name describes HOW not WHAT
  • Verifying through external means instead of interface
// BAD: Bypasses interface to verify
test("createUser saves to database", async () => {
  await createUser({ name: "Alice" });
  const row = await db.query("SELECT * FROM users WHERE name = ?", ["Alice"]);
  expect(row).toBeDefined();
});

// GOOD: Verifies through interface
test("createUser makes user retrievable", async () => {
  const user = await createUser({ name: "Alice" });
  const retrieved = await getUser(user.id);
  expect(retrieved.name).toBe("Alice");
});

Tautological tests: Expected value restates the implementation, so the test passes by construction.

// BAD: Expected value is recomputed the way the code computes it
test("calculateTotal sums line items", () => {
  const items = [{ price: 10 }, { price: 5 }];
  const expected = items.reduce((sum, i) => sum + i.price, 0);
  expect(calculateTotal(items)).toBe(expected);
});

// GOOD: Expected value is an independent, known literal
test("calculateTotal sums line items", () => {
  expect(calculateTotal([{ price: 10 }, { price: 5 }])).toBe(15);
});
Raw lines 25 to 77