Characterization tests: a solution for creating a test on legacy code

Daniele Scillia (Dan The Dev)
Dan the Dev
Published in
3 min readMar 25, 2021
Photo by Scott Graham on Unsplash

We’ve all had to deal with legacy code that had various design issues and wasn’t covered by tests. What to do in these situations?

First of all, let’s agree on definitions: with “legacy code” I mean any code that is not covered by automated testing, as suggested by Michael C. Feathers in his book: Working Effectively With Legacy Code.

When dealing with legacy code, the biggest mistake we can make is to accept the status quo and forgo automated testing. I dedicated a video to this topic on my Youtube channel (in Italian), where I presented a list of what I think are the most effective solutions to add a test to code that doesn’t have one, depending on the complexity and specificity of the case.

Here I want to go deep with the discussion on one of these solutions, Characterization Tests; I like this technique because it seems to be the best solution in most cases and they also have also other useful implications.

Characterization tests are very useful when we want to protect the behaviour of the system. We write a test that verifies the current behaviour in order to defend it from future inappropriate changes. To make sure we don’t make any mistakes when writing the test, we can follow these simple steps:

  1. Determine a piece of code to test
  2. Write a test with an assertion that you know is wrong
  3. Run the test and use the error message to find out how it actually works
  4. Change the test to check for correct behavior
  5. Repeat steps 2–4 until you are reasonably satisfied with the coverage of the tests
  6. Give the tests a meaningful name that expresses the behaviour they verify

Characterization tests are a technique I particularly like because I think they are also useful for the purpose of investigating the behaviour of a system we know little about and their nature is to work not on the whole system but on a part of it.

Moreover, to write the first test, the “definitely wrong” one, you’ll have to investigate the codebase a little bit, and then you’ll the error output and you need to interpret it to understand how to fix the assertion.

This cycle of “trial and error” leads to questioning the code and allows you a useful discovery to understand a section that you don’t know; of course it doesn’t mean you will become a master of that code, since tests only bring you to understand the surface, but it’s a starting point!

In this way, we’ll have covered of test the interesting part of the code and we will be able to work on it with greater serenity and safety. It’s an ideal solution when the legacy code concerns a codebase that is not well known to us, in my opinion, allowing us to cover it with tests and also learn a bit about that piece of code.

--

--

Daniele Scillia (Dan The Dev)
Dan the Dev

Software Engineer @TourRadar - Passionate Dev, XP Advocate, passionate and practitioner of Agile Practices to reach Technical Excellence