Michael Feathers defines legacy code as code without tests, and that definition is the one that matters here: a module checked in last week with no coverage is legacy in exactly the same sense as a module from years ago (DaedTech's write-up). Feathers' book lays out a change algorithm that puts tests before modification: identify the change point, find where behavior can be sensed, break dependencies to get the code into a harness, cover it with characterization tests, then add new behavior, then refactor (BSSw summary). The ordering is not stylistic. It exists because the code's current behavior is the only specification you have.
A characterization test is therefore a test that "characterizes the actual behavior of a piece of code" rather than asserting intended behavior (understandlegacycode.com). The practical consequence: if a function called addTwoNumbers(1, 1) returns 12, the test asserts 12. You do not correct it. Once code is in production, users have built processes around its exact outputs, however odd, so "different is dangerous" and the job is to detect difference, not to judge it (DaedTech).
This is why the technique suits agent-assisted change so well. An agent writing a test from a description will invent plausible expectations. An agent writing a test from observed output can only record what happened. Feathers describes characterization testing as a genuinely powerful use of AI test generation, precisely because the recording step removes the model's need to guess (Tech Lead Journal interview).



