Note: It may be helpful to read my previous post on the Golden Master Technique before seeing an example of its application here.
The Gilded Rose Kata
I encountered the Gilded Rose Kata after reading this blog post on Why Most Solutions to the Gilded Rose Miss The Bigger Picture. The Gilded Rose code kata is particularly appealing because it requires modification of a poorly written (simulated) code base. It is excellent practice for our typical daily work, yet it is still small enough to learn and jump in quickly.
I encourage you to try the Gilded Rose Kata before reading further. At a minimum, please read the overview and consider how you would approach the problem.
Please give it a try and come back…
Ok, you’re back. How would you start?
The Golden Master Technique
This type of problem is a great example of when the Golden Master technique can be a better starting point than going straight into unit tests.
The overview of Gilded Rose states that the existing application “works”. This describes a “real application”, working in production, that is useful enough to enhance with new features. Thus, it is as important, if not more so, to preserve existing behavior of the application as it is to add new features.
In order to unit test the code properly, we would need to modify the code somewhat significantly (in proportion to the existing code base) to be able to create the seams required for good unit tests. And to get complete coverage of the requirements would require a fair number of tests, some of which may not be obvious or explicit.
Applying the Golden Master Technique
So, let’s get started in creating our “golden master”. We need to execute a sufficient number iterations of the code to generate enough output to create a meaningful baseline. For this example, 50-100 iterations would be more than sufficient coverage. In a larger code base, we would need to create a more diverse range of input values. In this case, however, the initial setup appears sufficient to cover the necessary code paths.
To generate the Golden Master, we need to:
- Open/Create a file for saving the output
- Modify code to output state to the file, e.g. write the Item properties (Name/SellIn/Quality) out for each execution of UpdateQuality()
- Modify the code to iterate through through a sufficient number of days, 100 days for example
- Close/Save the file
Performing the above steps in code would not be difficult. However, this is even easier using the Approval Tests Library. The framework does exactly what we need:
- Runs a specified “test”
- At the end of the test, asserts/verifies a given state
- Compares the resulting execution, with an accepted master. If an accepted master doesn’t already exist, you accept & save it (or fix it until you are happy with it). If the master does exists, but is different, it fails the test. Which can again, either be fixed, or accepted as a new master.
To get started:
- Open the GildedRose solution
- Add/Install ApprovalTests into our solution. For .NET, the easiest way, of course, is via NuGet. Otherwise, it can be downloaded from here.
- Enhance the application to be able to capture its state, for example using a string representation of the items & their current values
- Create a test that will verify the state from the previous step
- Run and fix the tests until you are happy with the accepted golden master
How to enhance the application to capture state (step 3)
public string GetSnapshot()
{
var snapshot = new StringBuilder();
foreach (var item in Items)
{
snapshot.AppendLine(string.Format("Name: {0}, SellIn: {1}, Quality: {2}", item.Name, item.SellIn, item.Quality));
}
snapshot.AppendLine("------------------------");
return snapshot.ToString();
}
How to Create a test that will verify the state in the previous step (step 4)
1. Let’s create a basic approval test, simply to validate the state from the initial setup (before any iterations):
[Test]
[UseReporter(typeof(DiffReporter))]
public void TestThis()
{
var app = Program.Initialise();
var initialSetup = app.GetSnapshot();
Approvals.Verify(initialSetup);
}
To make our test compile and run…
2. Change the Program class to public…
public class Program
3. … and extract the initial setup into an Initialize() method:
public static Program Initialize()
{
return new Program
{
Items = new List
{
new Item {Name = "+5 Dexterity Vest", SellIn = 10, Quality = 20},
new Item {Name = "Aged Brie", SellIn = 2, Quality = 0},
new Item {Name = "Elixir of the Mongoose", SellIn = 5, Quality = 7},
new Item {Name = "Sulfuras, Hand of Ragnaros", SellIn = 0, Quality = 80},
new Item {Name = "Backstage passes to a TAFKAL80ETC concert", SellIn = 15, Quality = 20},
new Item {Name = "Conjured Mana Cake", SellIn = 3, Quality = 6}
}
};
}
Now, our Main() looks like this:
static void Main(string[] args)
{
System.Console.WriteLine("OMGHAI!");
var app = Initialise();
app.UpdateQuality();
System.Console.ReadKey();
}
4. Finally, we can add another test, to execute with a 100 iterations, which creates our “golden master”:
[Test]
[UseReporter(typeof(DiffReporter))]
public void TestThisTimes100()
{
var app = Program.Initialise();
var snapshotForHundredIterations = new StringBuilder();
var initialSnapshot = app.GetSnapshot();
snapshotForHundredIterations.Append(initialSnapshot);
for (int i = 0; i < 100; i++ )
{
app.UpdateQuality();
var currentSnapshot = app.GetSnapshot();
snapshotForHundredIterations.Append(currentSnapshot);
}
Approvals.Verify(snapshotForHundredIterations);
}
When, approval tests execute with the [UseReporter(typeof(DiffReporter))] attribute, the framework launches an installed diff tool. If you save the resulting file, formatted as SomeTestName.approved.txt, it becomes the accepted “golden master”.
That’s it! If you find it slightly confusing the first time, try a couple examples yourself. Once you understand the concept of the ApprovalTest framework, it is a simple and effective way to create a “golden master” test quickly.
At this point, you could proceed with the Gilded Rose Kata, making enhancements, or if desired, creating a set of explicit unit tests to more accurately describe the given requirements & for future readability & maintainability.
Other Resources