
🔸 TLDR
Characterization tests capture what legacy code actually does today—bugs and all—so you can refactor with confidence. First, lock in current behavior with tests; then improve the design under that protective net. 🛡️
🔸 WHAT IS A CHARACTERIZATION TEST?
▪️ A test that documents existing behavior (intended or accidental).
▪️ It’s not about “right vs wrong” yet—it's about “what happens now.”
▪️ Once behavior is pinned down, you can refactor safely and later change behavior deliberately.
🔸 WHEN TO USE THEM
▪️ Legacy code with no tests or unknown side effects.
▪️ Systems that are hard to change without breaking things.
▪️ Before refactoring, upgrading libraries, or optimizing performance.
🔸 HOW TO WRITE THEM (FAST PATH)
▪️ Find seams to call the code from outside (public APIs, endpoints, CLI, service methods).
▪️ Record current outputs/side effects (return values, DB rows, logs, HTTP calls).
▪️ Codify as assertions—even if the result looks odd, assert it anyway.
▪️ Stabilize flakiness (freeze time, seed randomness, stub I/O).
▪️ Name tests by scenario (Given_When_Then describes the observed behavior).
// Example (Java/JUnit)
@Test
void calculates_discount_for_loyal_customer_current_behavior() {
var price = service.finalPrice("loyal", 100.0);
// Yes, 17% looks odd—but it's what the system does today.
assertEquals(83.0, price);
}
🔸 GOOD PRACTICES
▪️ Test the outermost boundary first (API/feature level) to cover more behavior quickly.
▪️ Snapshot tricky outputs (JSON, HTML) and approve changes via review.
▪️ Log unknowns with TODOs, but keep the assert matching the reality.
▪️ Isolate side effects with fakes/mocks only where needed.
▪️ Refactor in small steps under the test net, then tighten expectations toward the desired behavior.
🔸 COMMON PITFALLS
▪️ Overfitting to noise (timestamps, nondeterminism) → normalize or freeze them.
▪️ Huge end-to-end tests only → add a few targeted seams for speed and clarity.
▪️ Changing behavior while characterizing → first lock, then change.
▪️ Brittle fixtures → build minimal, real-ish data that expresses the scenario.
🔸 HOW THIS ENABLES REFACTORING (STRATEGY)
▪️ Step 1: Characterize current behavior → green tests ✅
▪️ Step 2: Refactor design (naming, extract methods, break dependencies) → still green ✅
▪️ Step 3: Introduce intentional behavior changes with new/updated tests → safe evolution ✅
🔸 TAKEAWAYS
▪️ Characterization tests are a safety harness for legacy code.
▪️ Assert reality first, improve design second, change behavior last.
▪️ Small, fast, scenario-named tests make future changes cheaper and safer.
#LegacyCode #CharacterizationTests #Refactoring #UnitTesting #TDD #CleanCode #SoftwareCraftsmanship #Java #TestingStrategies #TechDebt