Skip to main content

Behavior Preservation Lab

The goal of this lab is to make behavior preservation and seam identification reflexive. You should be able to open a legacy function and, within 20 minutes, have it under characterization tests with at least one seam identified.

Retrieval Prompts

  1. State Fowler's definition of refactoring from memory.
  2. State the definition of a seam and of an enabling point.
  3. What is the difference between a characterization test and a unit test?
  4. Name the four triggers for refactoring.
  5. Why is "refactor and fix a bug" a bad commit?

Compare and Distinguish

Separate these pairs clearly, in sentences:

  • refactoring vs restructuring (Fowler's narrow vs loose use)
  • characterization test vs specification test
  • seam vs mock
  • Feature hat vs Refactor hat
  • preparatory refactoring vs scheduled "refactor sprint"

Common Mistake Check

For each statement, identify the error:

  1. "We stopped shipping features for two weeks while we refactored the pricing module."
  2. "My refactor changed observable behavior but I made all the tests pass by updating them."
  3. "I don't need tests -- my IDE's Rename refactor is automatic."
  4. "The characterization test is wrong; it encodes the current bug."
  5. "I introduced a seam by renaming the parameter."

Mini Application

Task A: Characterize and Prove

Here is a legacy function with no tests:

function classifyCustomer(customer, orders) {
if (!customer) return "INVALID";
let total = 0;
for (const o of orders) {
if (o.status === "paid") total += o.amount;
}
if (customer.type === "wholesale") total *= 1.0;
else total *= 0.95;
if (total > 10000) return "GOLD";
if (total > 1000) return "SILVER";
if (total > 100) return "BRONZE";
return "NEW";
}

Write at least 5 characterization tests covering:

  • null customer
  • wholesale customer with paid orders above 10,000
  • non-wholesale customer below 100
  • mix of paid and non-paid orders
  • exactly at a tier boundary (total = 1000)

Record the observed outputs exactly. Do not fix any behavior that looks wrong. Then Extract Function on the tier-classification (the if > 10000 ... else "NEW" block). Every test must still pass.

Task B: Find and Name a Seam

Here is legacy code that is hard to test:

async function orderSummary(orderId) {
const order = await db.query(`SELECT * FROM orders WHERE id = ?`, [orderId]);
const total = order.items.reduce((s, i) => s + i.price, 0);
const now = new Date();
return {
id: order.id,
total,
generatedAt: now.toISOString(),
};
}
  1. Name the two calls that make this hard to test.
  2. For each, introduce a seam using the smallest possible change.
  3. State the enabling point for each seam.
  4. Write a unit test that runs without touching the database or the real clock.

Evidence Check

This lab is complete only if:

  • you produced at least 5 characterization tests that all pass on the unmodified code
  • you applied at least one refactor move (Extract Function) with every test still passing
  • you named two seams and their enabling points in Task B
  • you can explain in one sentence why characterization tests freeze the current behavior, including bugs