Characterization Test for Legacy Python Code
Capture the exact output of a legacy Python function for known inputs, creating a characterization test that documents current behavior before refactoring.
Python code
32 linesdef legacy_behavior(value):
"""Legacy function that returns a tuple with unconventional types."""
if value == "special":
return None, "legacy-special"
elif value > 100:
return value, "large"
elif value > 0:
return value * 2, "positive-doubled"
elif value == 0:
return 0, "zero"
else:
return abs(value), "negative-abs"
def characterize_legacy_behavior():
"""Characterization test: capture and print actual legacy behavior for known inputs."""
test_cases = [("special",), (150,), (42,), (0,), (-7,)]
outputs = {}
for args in test_cases:
result = legacy_behavior(*args)
outputs[args] = result
# Print characterization results in a deterministic, readable format
for args, result in outputs.items():
print(f"legacy_behavior({args!r}) => {result!r}")
return outputs
if __name__ == "__main__":
characterize_legacy_behavior()
Output
legacy_behavior(('special',)) => (None, 'legacy-special')
legacy_behavior((150,)) => (150, 'large')
legacy_behavior((42,)) => (84, 'positive-doubled')
legacy_behavior((0,)) => (0, 'zero')
legacy_behavior((-7,)) => (7, 'negative-abs')
How it works
A characterization test records the actual behavior of existing code without asserting correctness — it locks in current output as a safety net. The function returns mixed types (None, int, str) in tuples, which the test captures as-is for each input. By using repr() in the output, the test shows exact Python representations, making any future changes obvious. This pattern is especially useful before refactoring legacy code, letting you verify that refactored output matches the original.
Common mistakes
- Assuming current behavior is correct — characterization tests capture behavior, not intended design
- Using equality assertions on tuples containing None without understanding the exact type contracts
- Forgetting to cover edge cases like zero, negatives, and special string values
- Modifying the legacy function while characterizing, which changes the baseline
Variations
- Use pytest with parametrize decorator to generate test cases dynamically
- Write a snapshot-style test with unittest.mock to store outputs for comparison
Real-world use cases
- Documenting undocumented legacy functions before migrating them to a new architecture.
- Creating a regression baseline when adding new features to mature codebases.
- Validating that a rewritten module produces identical outputs for production-critical paths.
Sponsored
More from Testing & modern typing
- Capture stdout and stderr with pytest capsys easy
- Dataclass with Type Hints Fields in Python easy
- Dependency Injection in Python for Testability easy
- Design Data Helpers with Python TypedDict and Literal easy
- Fix and Test a Regression Bug in Python with Unit Tests easy
- Format Data with Type Hints in Python easy
Keep learning
Related tutorials and quizzes for this topic.