Primary Menu

Building a test suite for legacy code can be daunting, so most of us usually approach legacy code in two different ways:

If it ain’t broke don’t fix it

Refactor without automated tests and hope for the best

But there is a third option that can give us the safety net provided by a good test suite and isn’t as slow as manually building a characterization test: using the Golden Master Technique to build the characterization tests.

Iteration Objective

In the earlier post I discussed that the first iteration of the legacy code retreat in which you have to explore the code and understand it’s purpose. As you may have guessed, the purpose of the second iteration is to build a test suite using the Golden Master technique. It’s a smaller iteration as it should last just 20 minutes, but I ran into some issues and it took me longer to figure out how to build the tests, probably because I’m doing these exercises alone and not pair programming as I should. Anyway, here is the procedure I followed to build the Golden Master:

Find all possible inputs, both direct and indirect

Find all possible outputs, both direct and indirect

Find a way to record every output for any given input

Play all inputs and verify that the output correspond to what has been previously recorded.

Find all the possible inputs

All inputs to the Game object are produced by the System.Random library so we have two ways to approach this. We can either record all the values generated by the Random object or we can find a way to have every time the same sequence of random numbers generated by it. The former approach has two disadvantages:

Too many inputs to log

I have to exclude the call to rand.GetNext() when testing

These two aren’t really huge impediments, but there is a way to solve the problem with fewer changes to the code: seeding the random number generator. How does this work? the System.Random class is a pseudo-random number generator and one of its properties is that given a seed, the sequence of numbers returned is deterministic so, if during our tests we seed the generator with the same number used while recording the data we can be sure that the random object will return the same sequence and thus the roll() method will be called with exactly the same values.

// GoldenMaster.cs

// The call to GameRunner

for (inti = 0; i &lt; 100; i++)

{

args[1] = i.ToString();

GameRunner.Main(args);

}

// GameRunner.cs

// the

varseed = int.Parse(args[1]);

//...

varrand = newRandom(seed);

The for loop in the code above is where I build the input sequence calling the Main function 100 times with different seeds for the random generator, in this way I have to log just the seed and not every number generated.

Find all possible direct or indirect outputs

To be able to verify that for any given input the program creates the same output I need to record all the outputs of the system. Luckily, the program doesn’t do any call to external libraries and doesn’t write anything to a database or hard drive, it just prints strings to the console, it has just one type of output: Console.Writeline.

Find a way to record every output for any given input

Knowing that all possible outputs are just calls to Console.Writeline simplifies a lot the step of recording the outputs for every given input. As we know Console.Writeline. is a static method and static methods are hard to stub, normally when you encounter a static method that you want to stub you wrap it in an isolated class or in a protected method of the same class and the you stub it in your tests. Isolating all calls to Console.WriteLine can lead you to introduce some bug in the program if you don’t have any test to check what you are doing.

Luckily there is a better way to stub the call to Console.WriteLine and it is to use to change the default output stream so it would write to a log file instead of printing to the console:

varfile = File.CreateText(&quot;gold.txt&quot;);

Console.SetOut(file);

// call to GameRunner.Main

file.Flush();

file.Close();

Play all inputs and check the results

After we changed the output to write to a text file, running our test will record all the inputs and outputs of 100 runs of the game and the resulting file will look like this:

**********

0

----------

Chet was added

They are player number 1

Pat was added

They are player number 2

Sue was added

They are player number 3

Chet is the current player

They have rolled a 4

Chet's new location is 4

The category is Pop

Pop Question 0

Question was incorrectly answered

Chet was sent to the penalty box

Pat is the current player

They have rolled a 4

Pat's new location is 4

The category is Pop

PopQuestion1

Answer was corrent!!!!

....

The category is Science

ScienceQuestion4

Answer was corrent!!!!

Sue now has 6GoldCoins.

__________

You can notice that in the beginning a game log start with a series of asterisks, then our seed for the random number generator then a series of dashes and then it starts recording all the outputs of the game. When the game ends I close the block with a series of underscores (which are probably useless but, who cares at this point?)

Testing the recorded data

After building our golden master we have to build a test suite that uses the recorded data to test that the system correctly behaves. The test built during this step is going to help us refactor the code giving us the safety net that ensures us that we didn’t break the current behavior while refactoring: we have a good amount of data representing what the behavior of the system should be and at every run, if the tests pass we are sure that the software is maintaining such behavior.

Like while building the golden master, during this test we want to access all the outputs for every possible inputs so we change console’s output stream, this times we don’t need to log anything so we just use a memory stream.

varÂ streamÂ =Â newÂ MemoryStream();

varÂ writerÂ =Â newÂ StreamWriter(stream);

Console.SetOut(writer);

Then we read the first input from the log and we execute the program

GameRunner.Main(new[] { "false", currentSeed });

Later we iterate through the log entries checking that the output recorded in the memory stream equals the one recorded in the file.

And if you don’t want to miss the next post SUBSCRIBE or a kitten will die somewhere.

Author: Daniele Pozzobon

Daniele is an aspiring software craftsman with more that ten years of experience in the software industry. He is currently a consultant in the .Net space for a big insurance company, and previously have worked as a web dev in the manufacturing industry. He has experience with C#, Java, C++, PHP, Javascript, and lately has added some F# to the sauce.
He constantly annoys his friends by talking about software and is passionate about Agile methodologies, which gives him more opportunities to talk annoy his friends even more.
When there are no friends around to annoy, he blogs on CodeCleaners and in his free he time loves go hiking with his wife and two daughters.