November 22, 2010

A Common Set of Test Refactorings

Originally published  19 Feb 2006

Today, I want to blog about what I think might be my most common set of low level Eclipse refactorings used together when I’m developing unit tests. I use this set to parameterize tests as discussed in Tabular Tests. Lately, I’ve been trying to use the Eclipse refactorings as much as possible, thinking that the automation would be less likely to break things than if I made the changes manually. It’s also interesting to make code changes without doing any typing. The set consists of:

  1. Extract Local Variable (possibly multiple times).
  2. Extract Method
  3. Inline the local variables created in step 1.
So let’s suppose I’m about to start work on a new method. I like to start with a brain dump of most, if not all of the test cases. This is to get me thinking about the desired behavior and also because it clears my head, so that when I start writing the code, I can focus. Once I’m ready, I start with a simple case.

Let’s take the Range.equals() example from the previous blog. My brain dump would have resulted in a table like this:

Min Max Equals?
same same true
different same false
same different false

Yes, I would have jotted down other, exceptional cases for things like null and a non-Range object, but that’s out of scope here. For this blog, I’m just concentrated on comparing Range objects. I start with the positive case and come up with something like:

   private static final int MIN = 1;
   private static final int MAX = 7;

   private Range range = new Range(MIN, MAX);


   public void testEqualsAnotherRange() {
      Range anotherRange = new Range(MIN, MAX);
      assertEquals(range, anotherRange);
   }



To be honest, I probably would have had the MIN and MAX constants hardcoded in there, but at some point, I would have performed Extract Constant to come up with the above.

After I get that test to pass (by returning true), I’m ready to write the next test. I want to pick something simple again (of course in this example they’re all simple). But before I write the next test, I do something that is probably considered cheating by the hardcore test-driven development crowd. I’m not too concerned because I know from experience that I’m going to end up with tabular tests. In fact, the table is specified above!  So here goes the set of refactorings:

I first Extract Local Variable on MIN and MAX:

   public void testEqualsAnotherRange() {
      int testMin = MIN;
      int testMax = MAX;

      Range anotherRange = new Range(testMin, testMax);
      assertEquals(range, anotherRange);
   }



Now I know from my table that I need a boolean for my expected equals. There's no simple Eclipse refactoring so I manually make the change:


   public void testEqualsAnotherRange() {
      int testMin = MIN;
      int testMax = MAX;
      boolean expectedEquals = true;
      Range anotherRange = new Range(testMin, testMax);
      assertEquals(expectedEquals, range.equals(anotherRange));
   }



I highlight the last two lines and perform Extract Method:

   public void testEqualsAnotherRange() {
       int testMin = MIN;
       int testMax = MAX;
       boolean expectedEquals = true;
       testEqualsAnotherRange(testMin, testMax, expectedEquals);
   }

   private void testEqualsAnotherRange(int testMin, int testMax,
           boolean expectedEquals) {
       Range anotherRange = new Range(testMin, testMax);
       assertEquals(expectedEquals, range.equals(anotherRange));
   }



I want to get back to what it looks like in the table, so I Inline each local variable:

   public void testEqualsAnotherRange() {
      testEqualsAnotherRange(MIN, MAX, true);
   }

   private void testEqualsAnotherRange(int testMin, int testMax,
         boolean expectedEquals) {
      Range anotherRange = new Range(testMin, testMax);
      assertEquals(expectedEquals, range.equals(anotherRange));
   }



I just created the first row of the table by applying the set of refactorings that is the topic of this blog. At this point, I usually look at the parameterized method and make sure I’m happy with the signature. I’ve already done most of this when I extracted the method, but Eclipse doesn’t let me make the method static, if that’s appropriate (in this case it’s not). Eclipse also lists all the checked exceptions, which for tests, I’d rather generalize to Exception.  This doesn't apply in this case either.

During this set of refactorings, I would have ran the tests after a change or two just to make sure I didn’t break anything. I also do that because it feels good to get that green bar.

This is a pretty trivial example. Most of the time much more is going on in the parameterized method, but this still shows the removal of duplication.

I could have made these changes manually, but like I mentioned above, I like the automation; I’m less likely to break things.

Okay, I can write the next test now:
   public void testEqualsAnotherRange() {
      testEqualsAnotherRange(MIN,     MAX, true);
      testEqualsAnotherRange(MIN - 1, MAX, false);
   }


I’ve got the red bar. Time to make it green.

No comments:

Post a Comment