Saturday, April 22, 2017

Readable Tests

There's a great little chapter in Growing Object Oriented Software Guided by Tests on how to write helper methods for building complex test data. The specific techniques are somewhat less relevant in a post AutoFixture world, but the chapter also makes a powerful case that test code should read as English prose.

The chapter walks through a couple of refactor steps of some code to construct test data for logic working with orders, starting with something like (I'm somewhat summarizing and C#-ing the original Java examples):

var order = new Order(
               new Customer(
                  new Address(...

This code is extracted into a builder class like this:

var order =  OrderBuilder()
               .fromCustomer(
                  new CustomerBuilder()
                    .withAddress(...

Then refactored to:

var order = anOrder()
              .fromCustomer(
                 aCustomer()
                   .withAddress(

And this builder is finally used in a helper method like this:

havingReceived(anOrder()
  .withLine("DearstalkerHat", 1)
  .withLine("Tweed Cape", 1);
  ...

Freeman and Pryce note:
We started with a test that looked procedural, extracted some of its behavior into builder objects, and ended up with a declarative description of what the feature does. We're nudging the test code towards the sort of language we could use when discussing the feature with someone else, even someone non-technical; we push everything else into supporting code.  ... We use test data builders to reduce duplication and make the test code more expressive. It's another technique that reflects our obsession with the language of code, driven by the principle that code is there to be read.
I recently had a chance to put this into practice while working on a test of a Sitecore RenderField pipeline processor, to document required behavior for Experience Editor mode.  I found a very helpful post by Dmitry Harnitski that documents how to create Editor mode with Sitecore.FakeDB, and verified that the syntax in the post worked, enabling me to create a failing test showing I had not yet implemented the Experience Editor logic. To get my test to pass, I just had to add a check on Experience Editor mode.  (Red: Editor mode should suppress a behavior, and my test showed it didn't.  Green: I added a check on IsInEditMode to the production code.)  There was not a lot to do with the "Refactor" step in the production code, but in the test code, I refactored the code from the blog post:

var fakeSiteContext = new Sitecore.FakeDb.Sites.FakeSiteContext(
new Sitecore.Collections.StringDictionary
{
  {"enableWebEdit", "true"},
  {"masterDatabase", "master"},
});
 
using (new Sitecore.Sites.SiteContextSwitcher(fakeSiteContext))
{ 
   Sitecore.Context.Site.SetDisplayMode(DisplayMode.Edit, DisplayModeDuration.Remember);
   ...  

to:

using(aSiteWithEditModeEnabled())
{
    SwitchToEditMode();

This is a small change, just gathering code into methods, but giving the methods names that make sense in the context of the test make the test much more expressive of its intent, and greatly raises the signal to noise ratio for anyone reading the test. "Code is there to be read."

No comments:

Post a Comment