Legacy Coderetreat: Part 13 – Unit Testing on Legacy Code

Unit Testing on Legacy Code

Blog post series

This blog post is part of a series about legacy coderetreat and legacy code techniques you can apply during your work. Please click to see more sessions about legacy code.

Purpose

In the previous episode post we extracted a class. The code we just extracted is tested at this moment only with System Tests. Now it’s the time to start writing unit tests, considering that we already have some system tests in place.

Unit Testing on Legacy Code

Unit Testing on Legacy Code

Concept

We have this class, newly extracted from the tangled code:

At this moment all the methods are static and not public.

Step 1: Try writing a unit test

We cannot call any of the methods from the class PlayerMessage, because they are not public.

Step 2: Make the methods from PlayerMessage public.

We only want to test public methods. We must therefore make the methods in PlayerMessage public, before we start testing them.

Step 3: Write the first basic unit test

When we write unit tests on new features we always want to follow the cycle:

  • Red (the test is written, but not the production code)
  • Green (the production code is implemented and makes the test pass)
  • Refactor (improve the structure of the code)

In this case we write unit tests on existing code, so we will want to see them green from the beginning. These are characterization unit tests. Characterization tests are a method of characterizing the system’s functionality with the use of automated testing.

This first test focuses only on the basic behaviour: using a correct player name, the message will be correct. In the next steps we will focus also on writing negative tests, tests that verify the system’s behaviour in exceptional solutions like having empty player name, a special alphabet, etc.

We want to start writing one unit test to be able to see how easy it is to write.

Step 4: Write the rest of the basic unit tests

Because it was clear the first unit test is so easy to write, we can write all the basic happy case tests.

Step 5: Add unit tests for special cases

Let’s say that we have some acceptance cases: we need to support diacritics for Romanian. So for that we need to write unit tests for these letters.

In this way we need to add tests for the method PlayerMessage.createWhenSentToPenaltyBox as well.

Step 6: Add unit tests to document possible bugs

Often in this stage we find cases where the system behaves in a possibly incorrect way. It is highly possible to find bugs. But we should not change the production code. We should just document the cases in a way, and then discuss with the product people about each case.

Here is one case that seems wrong: the system can create a message when the player name is empty:

I have created a special annotation called PossibleBug that I use to document the cases I need to talk with the product people. In this way I create a backlog of discussion, and I do not need to focus now on creating the list of possible issues. I can do that automatically afterwards.

In this case we can write the same type of tests for PlayerMessage.createWhenSentToPenaltyBox, in the case the player name is empty. The same for PlayerMessage.createWithNumber when the player number is zero or a negative number. All of these situations can generate possible bugs.

Step 7: Test that the production system still works

You can do this with the batch of automated tests you already have (system, component, unit, etc), but you should also do some manual testing.

Outcomes

We started with a class having non-public methods. The purpose of this session was to start covering the class with tests. In this moment we have short and clear unit tests, written in isolation of any slow dependencies. These tests are fast and we need to run then any time we will change the production code, to make sure we do not introduce defects.

In the same way as shown in the last blog posts, we can extract pure functions, extract classes and then cover these classes with unit tests.

Remarks

The classes are a lot easier to be unit tested if we extract pure functions from the initial class. Pure functions are usually simple, clear and short. This is why we can test them very fast and go on extracting the next class. Focusing on extracting small methods is important because it enables a good flow of extraction -> refactoring -> testing -> refactoring.

When writing the unit tests we always need to think how much we need to test. Some good hints to decide the tests we need to write are risk based testing, equivalence partitioning and behaviour slicing.

History

Unit testing started to exist some tens of years after the computers were invented. But it got more and more used after Kent Beck published his book Extreme Programming Explained (1999), where Unit Testing is considered one of the core practices of Extreme Programming (XP).

Code Cast

Please find here a code cast in Java about this session

Acknowledgements

Many thanks to Thomas Sundberg for proofreading this post.

Subscribe

If you want to receive an email when I write a new article, subscribe here:

Subscribe for new articles

Leave a Reply

Your email address will not be published. Required fields are marked *

Post Navigation