This Saturday I had the privilege of attending another code retreat, only it wasn’t just a normal everyday code retreat but a legacy code retreat! This code retreat was hosted by J. B. Rainsberger so big thanks to him!
Whats the difference?
The basic setup is pretty much the same. You pair with another coder and start an intensive 45 minutes session of coding. After each session there is a retrospective of what kind of stuff you tried and how well it went. The big difference in between a normal code retreat and a legacy code retreat is that you don’t start from scratch but you begin with an existing code base. It is then your task to refactor the code as best as you can in 45 minutes without breaking existing functionality.
Test as documentation and failsafe
The first session wasn’t that special. The code was handed to us and the purpose was to try and figure out what it does. I started out writing tests for the class with the most behavior and just started from top to bottom to figure out each individual behavior. I enjoyed this allot because not only do you get to cover the existing code with tests so you wouldn’t break the functionality when refactoring later, the tests in itself is an excellent way of discovering how the code base works and behaves.
After the first session a retrospective was held and it was surprising to see how many pairs actually didn’t even run the code! To be honest I didn’t run it too. Another surprising feat was that some couples even managed to find bugs in the code from the first couple of unit tests!
The golden master
So then came the second session. Here we were asked to write end to end tests with a technique called the golden master. The idea behind this is that you save the output of the program to a separate file. There should be enough variation in the input of your parameters to try and cover as much of your program as possible. As a rule for the code handed by us, we wrote away 10.000 different variations of the parameters. The output you’ve written away should be confirmed as the correct behavior of the program as it will be used as a reference for the next tests.
When you refactor the code you can then run the golden master to save the output of the refactored code. The correctly verified output will then be compared to the output of the refactored code and any differences between the two will be displayed.
This technique is very useful for when you have an application with zero tests in it and the code is too coupled to test. You can then slightly change the code to make unit testing easier and still have a small safe-zone in which you can verify the correctness of your application.
Some couples (including me again -_-‘ ) tried to save all the possible values the parameters could form. This turned out to be a mistake as the variations ran up to a couple of millions.
I did have one issue though and that is that if you weren’t able to get the end to end tests done in this exercise you didn’t have a backup testing mechanism in the other sessions. I addressed this to JB and he said he was going to plan to upload the Golden Master so that next code retreat this wouldn’t be a problem any more.
Mocking behavior by sub classing
This technique was already known to me but I didn’t know it would prove as useful as it did when dealing with legacy code. Subclass To Test… basically the name says it all. When to code has a strong coupling to it and mocking behavior of the class proves difficult we can make the methods of that class virtual (overridable/@override). We can then subclass this class and override the methods that we want to mock in order to test our initial method. I’ll demonstrate this by some code:
Say we have the following legacy code:
public class BootyShaker
{
private List<string> _booties;
public int NrOfBooties
{
get { return _booties.Count(); }
}
public void CreateSomeBooties()
{
_booties = new List<string>();
// Legacy code should contain magic numbers!!!
for (int i = 0; i < 12; i++)
{
_booties.Add(CreateBooty());
}
}
/// <summary>
/// Disclaimer: I do not take any responsibility for suicides as a consequence of utilizing this method
/// </summary>
/// <returns></returns>
public string CreateBooty()
{
string thaBootie = string.Empty;
// Some complex behavior
return thaBootie;
}
}
So we want to test the CreateSomeBooties method but the developer didn’t make it easy for us. To make matters even worse the CreateBootie method is called by a couple of dozen other methods that we haven’t tested yet! So in order to mock the behavior without interfering with the rest of the untested (!) code we’ll subclass the BootyShaker.
public class BootyShaker
{
private List<string> _booties;
public int NrOfBooties
{
get { return _booties.Count(); }
}
public void CreateSomeBooties()
{
_booties = new List<string>();
// Legacy code should contain magic numbers!!!
for (int i = 0; i < 12; i++)
{
_booties.Add(CreateBooty());
}
}
/// <summary>
/// Disclaimer: I do not take any responsibility for suicides as a consequence of utilizing this method
/// </summary>
/// <returns></returns>
public virtual string CreateBooty()
{
string thaBootie = string.Empty;
// Some complex behavior
return thaBootie;
}
}
[TestFixture]
public class BootyShakerTest
{
[Test]
public void CreateSomeBooties_should_create_12_booties()
{
var bootyShaker = new BootyShakerSubclassTest();
bootyShaker.CreateSomeBooties();
Assert.AreEqual(bootyShaker.NrOfBooties, 12);
}
}
public class BootyShakerSubclassTest : BootyShaker
{
public override string CreateBooty()
{
return "Mah booty";
}
}
We need to make the method that we want to mock virtual (or overridable) so we can change the behavior in the subclass. Then when we have successfully mocked the behavior we can write a test for the original method. When the test is written we can refactor CreateSomeBooties.
Delegate the call by using Subclass To Test
In the session after that we went a step further. Now that we’ve created the base class that mocks the behavior of the CreateBooty method we can refactor this method to the appropriate type and delegate the call. Phew… I think I’ve lost you there :-P. I’ll try to speak the universal language then… code:
So simply put the CreateBooty behavior doesn’t belong inside the BootyShaker so we’re going to try and extract the behavior out of the class. So the first thing we need to do is inject the new type into the original BootyShaker:
public interface IBootyCreator
{
string CreateBooty();
}
…
private readonly IBootyCreator _bootyCreator;
public BootyShaker(IBootyCreator bootyCreator)
{
_bootyCreator = bootyCreator;
}
Don’t forget to adjust your base class to so you can still run your test:
public BootyShakerSubclassTest(IBootyCreator creator):base(creator)
{
}
Then refactor away the code in our base class inside a mock:
public class MockBootyCreator : IBootyCreator
{
public string CreateBooty()
{
return "Mah booty";
}
}
Then delegate the call in our BootyShaker:
public virtual string CreateBooty()
{
return _bootyCreator.CreateBooty();
}
And then remove the base class and use the original Bootyshaker with the injected
MockBootyCreator:
[Test]
public void CreateSomeBooties_should_create_12_booties()
{
var bootyShaker = new BootyShaker(new MockBootyCreator());
bootyShaker.CreateSomeBooties();
Assert.AreEqual(bootyShaker.NrOfBooties, 12);
}
So now you’ve coupled away the responsibility of creating booties to a new type!
Extracting pure functions
The next session was a bit different. This time we had to refactor by extracting pure functions. The good thing about doing this is that you do not alter the state of the object in which you’re working thus this makes it very easy to test.
This wasn’t really new to me and I try to do this as much as possible since working without state reduces allot of the risk and makes testing allot easier.
Actually do stuff
The last 2 sessions JB decided to do something different. This time we weren’t required to throw away our code so we could actually get some work done! To be honest, at the end of the day my head felt like it was going to explode so I needed the two extra sessions to be a bit more productive.
The problem I had with the two sessions is that you still had to switch pairs. You would then have to adjust to the code of your new partner. I would think it would be better if it was just one extra-long session since it takes a couple of minutes to get into each other’s mindset.
So that was it for the legacy code retreat. Big thanks to Erik for the organization and to JB for taking the time to guide us :-).
Join us next time! Till then...
wegschrijven naar een file is "save", niet "write away"... ;-)
BeantwoordenVerwijderenYeah, might need to polish my writing skills. Edited it :-)
BeantwoordenVerwijderen