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
- State Fowler's definition of refactoring from memory.
- State the definition of a seam and of an enabling point.
- What is the difference between a characterization test and a unit test?
- Name the four triggers for refactoring.
- 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:
- "We stopped shipping features for two weeks while we refactored the pricing module."
- "My refactor changed observable behavior but I made all the tests pass by updating them."
- "I don't need tests -- my IDE's Rename refactor is automatic."
- "The characterization test is wrong; it encodes the current bug."
- "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(),
};
}
- Name the two calls that make this hard to test.
- For each, introduce a seam using the smallest possible change.
- State the enabling point for each seam.
- 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