Monday, September 30, 2013

When to Create a SetUp Method

I recall from back when the Earth was cooling and I was spending a of time in Seattle (read: "living there") that there was a lot of advice flying around about the forces surrounding designing individual tests.

A particular point of interest was when to factor stuff out into a setup method.  One camp said "never factor something into a setup method until it's needed by more than one test."  Another camp said "factor stuff out into a setup method as soon as you reasonably can."

At the time, I didn't really care so I just went along with my friends.  Now I think I do care.  I've noticed that factoring certain things out into a set up method improves the readability of a test, even when there is only one.

That, to me, settles the "when" question by obviating it and uncovering a much more interesting question: what should you factor out into a set-up method?

I've noticed that, for me, there are two kinds of set-up needed: context stuff and test-specific stuff.  The context stuff is obvious.  The test-specific stuff is interesting.

For a human, there is no information in a context-setting line of code.  It is still informative to a compiler, which is why you need it, but a person trying to understand an API would easily fill in the blanks if he never saw that line of code.  The only real effect such a line of code has on a human-reader is to make a test less-readable by adding noise.  An example of context-setting set-up is instantiating the class being tested.

By contrast, the test-specific line of code is very interesting to a human.  It explains what is happening or why it is happening.  Examples of test-specific code would be invoking a method on the class under test or asserting on the results of such an invocation.

Let's look at two examples.

Here's a test with all the set-up in the test method to prevent a preemptive refactor:

[Test]
public void UnderOrdinaryCircumstancesPassesThroughToCore()
{
  var core = new Mock<PathFinder>();
  var specialEdge = new Mock<PathFinderEdge>();
  var specialEdgeFinder =
    SpecialFirstEdgePathFinder.GetInstance(core.Object, specialEdge.Object);
  var origin = Design.Element.GetInstance(null, null, null);
  var keyPath = new[] { Any.Symbol, Any.Symbol };
  var elementPath = new[] {
    Design.Element.GetInstance(null, null, null),
    Design.Element.GetInstance(null, null, null), };
  core.Setup(c => c.Navigate(origin, keyPath)).Returns(elementPath);

  var actual = specialEdgeFinder.Navigate(origin, keyPath).ToArray();

  Assert.That(actual, Is.EqualTo(elementPath));
}

Here's a test with all the set-up in the test method to prevent a preemptive refactor:

[Test]
public void UnderOrdinaryCircumstancesPassesThroughToCore()
{
  var keyPath = new[] { Any.Symbol, Any.Symbol };
  var elementPath = new[] {
    Design.Element.GetInstance(null, null, null),
    Design.Element.GetInstance(null, null, null), };
  core.Setup(c => c.Navigate(origin, keyPath)).Returns(elementPath);

  var actual = specialEdgeFinder.Navigate(origin, keyPath).ToArray();

  Assert.That(actual, Is.EqualTo(elementPath));
}

The two tests specify the exact same thing.  The only difference, in my mind, is how they read.  To me, the latter is a lot more clear than the former.

The process that works for me is as follows:
  1. Write the test with no set-up to discover what the test should be
  2. Make the test pass
  3. In the refactoring phase, move all the "clutter" to a context-setting setup method
I'm open to other arguments but this is working for me now.