Self Shunt

The self-shunt method of testing has been conflicting for me. Self-shunt is a good testing pattern to test observers/views. A colleague of mine has started to convince me that self-shunt has some serious drawbacks.

I want to explore the different ways to address the same pattern and how they fit into the test process and the test content. Are certain patterns easier to write and certain ones have a better final form?

Lets look at some code from Michael Feathers self shunt paper (see bottom for link). I extended it to do the mocking examples. The user story we are working with is the name of an item must be displayed on the led when the item is scanned.

Writing all of the tests did not require a concrete window (Display). We didn't need to create the stub of the file to use; we could just use the interface. All the test patterns let the test be isolated in the test class/interface/production class triad.

As a developer, it is easier for me to produce faster without quality loss if my context of thought is limited as much to the scope of the test as possible. Each extra window I switch is more contexts to deal with.

Here are three different tests:

 1 public class ScannerTest extends TestCase implements Display
 2 {
 3     private Item lastItem;
 4 
 5     public void testScan()
 6     {
 7         Item item = new Item("corn flakes");
 8         Scanner scanner = new Scanner(this);
 9         scanner.scan(item);
10         assertEquals(item,lastItem);
11     }
12 
13     public void displayItem(Item item)
14     {
15         lastItem = item;
16     }
17 }

The first test is making user of self-shunt test. Self-shunt was a quick way to start writing a test. The implementation on the interface in the test class will give you a good idea on what the implementation of the concrete class should look like when you get there.

Also, the test is simple and the syntax is expressive. However, the scalability is questionable. If you had more than one self-shunted interface in a single test file, it would be hard to start differentiating between which variables are used in which.

Also, moving around the file to get a good sense of what a test is doing is mis–directional and distracting. Ideally, I want to see the entire test on one screen, so I can read it. If there were many implemented methods in a test file that were not tests or helpers, there is the potential to be confused.

 1 public class ScannerTest   extends TestCase
 2 {
 3     public void testScan()
 4     {
 5         Item item = new Item("corn flakes");
 6         Display mockDisplay = new MockDisplay();
 7         Scanner scanner = new Scanner(mockDisplay);
 8         scanner.scan(item);
 9         assertEquals(item,((MockDisplay)mockDisplay).lastItem);
10     }
11 }
12 
13 public class MockDisplay implements Display
14 {
15     public Item lastItem;
16     public void displayItem(Item item)
17     {
18         lastItem = item;
19     }
20 }

Then I wrote the hand mock test. This test got rid of the clutter of implementing interfaces in the test and having local variables. Also, there are no external libraries needed, like a dynamic mocking library.

The test is simple and easy to read. The biggest drawback is dealing with the hand mock test file. It is untested code that I have seen a tendency for behavior to slip into, which is disastrous.

It can cause a bug in your tests that take too long to track down. Ideally this doesn’t happen, but I have seen it many times.

 1 public class ScannerTest extends TestCase
 2 {
 3    public void testScan()
 4    {
 5        MockControl control = MockControl.createControl(Display.class);
 6        Display mockDisplay = (Display) control.getMock();
 7        Item item = new Item("");
 8        mockDisplay.displayItem(item);
 9        control.replay();
10        Scanner scanner = new Scanner(mockDisplay);
11        scanner.scan(item);
12        control.verify();
13    }
14 }

Finally the dynamic mock test was written. This test was the simplest to write as a matter of process also the fewest lines of code. I never needed to leave the scope of the test function.

It was just setting up my expectations and letting the test run. As Micah Martin pointed out to me, the test execution seems backwards and unreadable with dynamic mocks.

The verification statements are at the beginning of the method and the execution is at the end. This is different than all the other tests in the system that follows an intuitive flow cycle of build, operate, and check.

With a little investigation I found Michael Feathers paper on Self Shunt.

Paul Pagel, Chief Executive Officer

Paul Pagel has been a driving force in the software craftsmanship movement since its inception.