Characterization tests are an attempt to lock existing behavior into an untested or undocumented system. They are described briefly in [[MichaelFeathers]] book Working Effectively With LegacyCode, among other places.
It may be impossible to know the "correct" behavior or safe way to refactor when making changes to or around legacy code that has no tests. This is especially true when no specification is available, or when other code may rely on flaws in the code. In these situations, it is typically best to assume that the old behavior must be preserved unless explicit changes are required. In order to preserve the old behavior, characterization tests are written that use the system as it has already been used.
If you observe that the legacy code gives a certain output based on an input, a characterization test sets the input, then asserts that the output matches the legacy result. For example, if you observe that MyLegacyFunction(123) == 456, you could include this as a characterization test.
It has been stated in a certain mailing list that there are an unlimited number of characterization tests that are needed. In almost all real-world situations it is difficult to test every possible input and verify it against every possible output. When code is available it is possible (although sometimes difficult) to ensure that the code paths and logic variations are tested. When it is difficult or impossible to ensure that a function is fully covered by test cases, such as a foreign library, test writers must use their judgment to decide how much testing is appropriate.
In legacy or foreign code, it is often sufficient to write characterization tests that only cover the specific inputs and outputs that are known to occur, paying special attention to edge cases.
Another usage of CharacterizationTest is LearningTest.