Characterization, an easier way to write tests

Filip Razek
2 min readMar 5, 2023

Testing is hard. Testing someone else’s code is even harder. But characterization tests make it easier!

We all know testing our code is important, and sometimes we even do it. But when you have to interact with code someone else has written in the past, but not documented, you’re much more likely to hope it does its job, or rewrite it if it doesn’t seem to.

However, there is a type of test made for this purpose — characterization tests! Unlike traditional testing, where you test the code against a known specification, characterization tests assume the code is correct, and test your changes against old behavior. That is, they simply detect changes and warn you about them

Example

Suppose you stumble upon this function (in a groceries list app, for example):

An obscure conversion function

This is sadly very poorly written code, and it isn’t easy to guess what the function is doing just by looking at it. Ideally, we would like to refactor it, but without testing in place we might break the functionality without even knowing it.

This is where we can use characterization tests. Luckily, we found correctly formatted inputs the function is supposed to convert:

Inputs we found for our function

We can simply run the function on them, store the outputs and create tests to give us confidence in our changes.

Choose a test framework

Just about any test framework will do the job, so make sure to use your favorite. Since the code is written in Javascript, I chose to use jest, but mocha or Jasmine are great too.

Sidenote: You can even use wish (not Shopping Made Fun), which is probably the easiest to set up and even includes an option for characterization tests.

Get the outputs

Running the code once allows us to map our inputs to outputs:

[["apple", 700, "g"], ["pear", "750", "g"]]: {"apple": 700, "pear": 750}
[["apple", "1", "pound"], ["pear", 2, "kg"]]: {"apple": 454, "pear": 2000}
[["apple", "1", "kg"], ["orange", 2200, "g"], ["apple", 1, "pound"], ["pear", "780", "g"]]: {"apple": 1454, "orange": 2200, "pear": 780}

Then we can write our tests:

Our characterization tests

Refactoring

Now that we have established our test suite, we can finally refactor without breaking the behavior! Every time we make a change, the passing tests will give us confidence in what we are doing.

For example, here’s how I refactored the function to make it clearer and more modular:

A refactored implementation of our groceries function

Thanks!

Still reading? You must be pretty good at paying attention!

I would love to hear your feedback on my other projects, so feel free to look at my GitHub ;)

Thanks for reading and happy coding!

--

--

Filip Razek
Filip Razek

Written by Filip Razek

A CentraleSupélec student living in the Czech Republic. Check out my other projects at https://github.com/FilipRazek/

No responses yet