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.

Medium Python 3.9+ Aug 9, 2026 Testing & modern typing 14 views 0 copies

Python code

32 lines
Python 3.9+
def 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

stdout
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

  1. Use pytest with parametrize decorator to generate test cases dynamically
  2. 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

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Testing & modern typing

Related tutorials and quizzes for this topic.