Net Objectives

Net Objectives
If you are interested in coaching or training in ATDD or TDD please click here.

Wednesday, July 11, 2012

Testing the Chain of Responsibility, Part 1 (redux)

Download the podcast

Testing the Chain of Responsibility

The Chain of Responsibility pattern (hereafter CoR) is one of the original “Gang of Four” patterns.  We’re assuming you know this pattern already, but if not you might want to read about it first at the Net Objectives Pattern Repository.

The basic idea is this: you have a series of rules (or algorithms) that are conceptually the same.  Only one of the rules will apply in a given circumstance.  You want to decouple the client objects that use the rules from:

  1. The fact that there is more than one rule
  2. How many rules there are
  3. How each rule is implemented
  4. How the correct rule is selected
  5. Which rule actually acted on any given request

All the clients should see/couple to is the common interface that all rules export, and perhaps the factory that creates them (or, from the clients’ perspective, the factory that creates “it”).

The CoR, in its classic form [1] accomplishes this by chaining the rules together, and handing a reference to the first one (in an upcast to the shared abstraction) to the client. When the client requests the action, the first rule decides “for itself” if it should act.  We call this “electing”.  If the rule elects, it performs the action and returns its result.  If it does not elect, it delegates to the next rule in the chain, and so on until some rule elects.  Regardless of which rule elects, the result is propagated back up the chain to the client. Typically only one rule will elect, and when one does we stop asking the rules that follow it; it just acts and returns, and we’re done.

Let’s examine a concrete example, and look at the design and some code.  We’ll keep the example very simple, so the pattern and testing techniques are easy to see.

Problem Statement

We have to process an integer.  There are two ways of processing it: a processing algorithm that is appropriate for “small” values (which are defined in the domain as any value in the range of 1 - 10000) and a different algorithm that is appropriate for “large” values (10001 - 20000).  Values over 20000 are not allowed.

Again, for simplicity, we’ll say that the large processor algorithm halves the value it is given, while the small processor doubles it.  If neither processing algorithm is appropriate, the system must throw an exception indicating an unsupported value was given.

Using the CoR


The classic CoR design view of this problem would look like this:

The Classic Chain of Responsibility

The Code

public abstract class Processor {
    public const int MIN_SMALL_VALUE = 1;
    public const int MAX_SMALL_VALUE = 10000;
    public const int MIN_LARGE_VALUE = 10001;
    public const int MAX_LARGE_VALUE = 20000;

    private readonly Processor nextProcessor;

    protected Processor(Processor aProcessor) {
       nextProcessor = aProcessor;
    }

    public int Process(int value) {
           int returnValue = 0;

           if(ShouldProcess(value)) {
               returnValue = ProcessThis(value);
           } else {
               returnValue = nextProcessor.Process(value);
           }
           return returnValue;
    }

    protected abstract bool ShouldProcess(int value);
    protected abstract int ProcessThis(int value);
}

Note the use of the Template Method Pattern [2] in this base class.  This eliminates the otherwise redundant part of the “decision making” that all the various processors would share, and delegates to the two abstract methods where the specific implementation in each case will be supplied in the derived classes.

Here they are:

public class LargeValueProcessor : Processor {
    public LargeValueProcessor(Processor aProcessor) :
base(aProcessor){}

    protected override bool ShouldProcess(int value) {
           if (value >= MIN_LARGE_VALUE && 
                value <= MAX_LARGE_VALUE)
    return true;
           return false;
    }

    protected override int ProcessThis(int value) {
           return (value/2);
    }
}

public class SmallValueProcessor : Processor {
    public SmallValueProcessor(Processor aProcessor) :
base(aProcessor){}

    protected override bool ShouldProcess(int value) {
           if (value <= MAX_SMALL_VALUE && 
                value >= MIN_SMALL_VALUE)
     return true;
           return false;
    }

    protected override int ProcessThis(int value) {
       return (value * 2);
    }
}

public class TerminalProcessor : Processor {
    public TerminalProcessor() : base(null){ }

    protected override bool ShouldProcess(int value) {
           return true;
    }

    protected override int ProcessThis(int value) {
           throw new ArgumentException();
    }

}

In testing this pattern, we have a number of behaviors to specify:

Common Chain-Traversal Behaviors
  1. That a processor which elects itself will not delegate to the next processor
  2. That a processor which does not elect itself will delegate to the next processor, and will forward the parameter(s) it was given unchanged
  3. That a processor which did not elect will “hand back” (return) any result returned to it from the next processor without changing the result

Individually Varying Processor Behaviors
  1. That a given processor will choose to act (elect) when it should
  2. That a given processor will not elect when it shouldn't
  3. That upon acting, the given processor will perform its function correctly

Chain Composition Behaviors
  1. That the chain appears to be the proper abstraction to the client
  2. The chain is made up of the right processors
  3. The processors are given “a chance” in the right order (if this is important)

Common Chain-Traversal Behaviors

All these behaviors are implemented in the base class Processor, via the template method, to avoid redundancy.  We don’t want redundancy in the tests either, so the place to specify these behaviors is in a test of one entity: the base class.  Unfortunately, the base class is an abstract type and thus cannot be instantiated.  One might think “well, just pick one of the processors -- it does not matter which one -- and write the test using that.  All derived classes can access the behavior of their base class, after all.”

We don’t want to do that.  First of all, it will couple the test of the common behaviors to the existence particular processor we happened to choose.  What if that implementation gets retired at some point in the future?  We’ll have to do test maintenance just because we got unlucky.  Or, what if a bug is introduced in the concrete processor we picked?  This could cause the test of the base behavior to fail when the base class is working just fine, due to the inheritance coupling.  That would be a misleading failure; we never want our tests to lie to us.  Coupling should always be intentional, and should always work for, not against us.

Here’s another good use for a mock.  If we make a mock implementation of the base class, it, like any other derived class, will have access to the common behavior.

class MockProcessor : Processor {
    public bool willElect = false;
    public bool wasAskedtoProcess = false;
    public int valueReceived = 0;
    public int returnValue = 0;

    public MockProcessor(Processor aProcessor) : 
           base(aProcessor){}

    protected override bool ShouldProcess(int value) {
           wasAskedtoProcess = true;
           valueReceived = value;
           return willElect;
    }

    protected override int ProcessThis(int value) {
           return returnValue;
    }
}

Note we keep this as simple as possible.  This is really part of the test, and will thus not be tested itself.  In fact, if we didn’t need it for two different tests, we’d probably make it an inner class of the test (which we call an inner shunt.  More on shunts later).

The tests that specify the proper chain-traversal behavior are simply conducted with two instances of the mock, chained together.  The first can be told to elect or not, and the second can be examined to see what happens to it with each scenario.

The first scenario concerns what should happen if the first process does not elect, but delegates to the second processor:

[TestClass]
public class ProcessorDelegationTest {
    private MockProcessor firstProcessor;
    private MockProcessor secondProcessor;
    private int valueToProcess;
    private int returnedValue;

    [TestInitialize]
    public void Init() {
           // Setup
           secondProcessor = new MockProcessor(null);
secondProcessor.willElect = true;
           firstProcessor = new MockProcessor(secondProcessor);
firstProcessor.willElect = false;
           valueToProcess = Any.Value; // [3]
           secondProcessor.returnValue = Any.Value;

           // Common Trigger
           returnedValue = 
                  firstProcessor.Process(valueToProcess);
    }

    [TestMethod]
    public void TestDelegationHappensWhenItShould() {
           Assert.IsTrue(secondProcessor.wasAskedtoProcess);
    }

    [TestMethod]
    public void TestDelegationHappensWithUnchangedParameter() {
           Assert.AreEqual(valueToProcess,
secondProcessor.valueReceived);
    }

    [TestMethod]
    public void TestDelegationHappensWithUnchangedReturn() {
           Assert.AreEqual(returnedValue,
secondProcessor.returnValue);
    }
}

These tests specify the three aspects of a processor that does not elect.  Note each aspect is in its own test method.  By telling the first mock not to elect, we can inspect the second mock to ensure that it got called, that it got the parameter unchanged, and that whatever it returns to the first mock is propagated back out with being changed.

The second scenario is where the first processor does elect.  All we need to prove here is that it does not delegate to the second processor.  Whether it does the right thing, algorithmically, will be specified in the test of the actual processors (we’ll get to that)..

[TestClass]
public class ProcessorNonDelegationTest {
    [TestMethod]
    public void TestNoDelegationWhenProcessorElects() {
           MockProcessor secondProcessor = 
                new MockProcessor(null);
           MockProcessor firstProcessor =
new MockProcessor((secondProcessor));
           firstProcessor.willElect = true;

           firstProcessor.Process(Any.Value);

           Assert.IsFalse(secondProcessor.wasAskedtoProcess);
    }
}

At first this might seem odd.  We’re writing tests that only use mocks?  That seems like a snake eating itself... the test is testing the test.  But remember, when the classloader loads a subclass (in this case, the mock), it also loads an instance of the base class in the background, and the base class is where the behavior we’re specifying actually exists.  We’re not testing the mock, we’re testing the template method in the abstract base class through the mock.

Individually Varying Processor Behaviors

Now that we’ve specified and proven that the delegation and traversal issues are correct, we now only have two things to specify in each individual processor: that it will elect only when it should, and that it will process correctly when it does.  The exception is the Terminal processor which, of course, should simply always elect and always throw an exception.

The problem here is that the only public method of the concrete processors is the Process() method, which is established (and tested) in the base class.  It would be a mistake, and a rather easy one to make, to write the tests of the concrete processors through the Process()method.  Doing so would couple these new tests to the ones we’ve already written, and over the long haul this will dramatically reduce the maintainability of the suite.

What we need to do is to write tests that directly access the protected methods ShouldProcess() and ProcessThis(), giving them different values to ensure they do what they are specified to do in the case of each concrete prossor.  Normally, such methods would not be accessible to the test, but we can fix this, simply, by deriving the test from the class in each case.  For example:

[TestClass]
public class SmallValueProcessorTest : SmallValueProcessor {
    public SmallValueProcessorTest():base(null){}

    [TestMethod]
    public void TestSmallValueProcessorElectsCorrectly() {
          Assert.IsTrue(
                 ShouldProcess(Processor.MIN_SMALL_VALUE));
          Assert.IsFalse(
                 ShouldProcess(Processor.MIN_SMALL_VALUE-1));
          Assert.IsTrue(
                 ShouldProcess(Processor.MAX_SMALL_VALUE));
          Assert.IsFalse(
                 ShouldProcess(Processor.MAX_SMALL_VALUE+1));
    }

    [TestMethod]
    public void TestSmallValueProcessorProcessesCorrectly() {
           int valueToBeProcessed =
Any.ValueBetween(Processor.MIN_SMALL_VALUE,
Processor.MAX_SMALL_VALUE);
           int expectedReturn = valueToBeProcessed * 2;
           Assert.AreEqual(expectedReturn,
this.ProcessThis(valueToBeProcessed));
    }
}

Note we have to give our test a constructor, just to satisfy the base class contract (chaining to its parameterized constructor, passing null).  If you dislike this, and/or if you dislike the direct coupling between the test and the class under test, an alternative is to use a testing adapter:

[TestClass]
public class LargeValueProcessorTest {
    private LargeValueProcessorAdapter testAdapter;

    [TestInitialize]
    public void Init() {
           testAdapter = new LargeValueProcessorAdapter();
    }

    [TestMethod]
public void TestLargeValueProcessorElectsCorrectly() {
         
           Assert.IsTrue(
testAdapter.ShouldProcess(
Processor.MIN_LARGE_VALUE));
           Assert.IsFalse(
testAdapter.ShouldProcess(
Processor.MIN_LARGE_VALUE - 1));
           Assert.IsTrue(
testAdapter.ShouldProcess(
Processor.MAX_LARGE_VALUE));
           Assert.IsFalse(
testAdapter.ShouldProcess(
Processor.MAX_LARGE_VALUE + 1));
    }

    [TestMethod]
    public void TestLargeValueProcessorProcessesCorrectly() {
int valueToBeProcessed =
Any.ValueBetween(Processor.MIN_LARGE_VALUE,
Processor.MAX_LARGE_VALUE);
           int expectedReturn = valueToBeProcessed / 2;
           Assert.AreEqual(expectedReturn,
testAdapter.ProcessThis(valueToBeProcessed));
    }

    private class LargeValueProcessorAdapter : 
               LargeValueProcessor {
           public LargeValueProcessorAdapter() : base(null) { }

           public new bool ShouldProcess(int value) {
               return base.ShouldProcess(value);
           }

           public new int ProcessThis(int value) {
               return base.ProcessThis(value);
           }
    }
}

We leave it up to you to decide which is desirable, but we’d recommend you pick one technique and stick with it.

Note that the first test method (TestLargeValueProcessorElectsCorrectly()) is a boundary (range)  test, and the second test method (TestLargeValueProcessorProcessesCorrectly()) is a test of a static behavior.  Refer to our blog on Test Categories for more details, if you’ve not already read that one.

FInally, we need to specify the exception-throwing behavior of the terminal processor.  This could be done either through direct subclassing or via a testing adapter; we’ll use direct subclassing for brevity:

[TestClass]
public class TerminalProcessorTest : TerminalProcessor
{
    [TestMethod]
    public void TestTerminalProcessorAlwaysElects()    {
           Assert.IsTrue(ShouldProcess(Any.Value));
    }

    [TestMethod]
    public void
TestTerminalProcessorThrowsExceptionWhenProcessing() {
           try
           {
               ProcessThis(Any.Value);
               Assert.Fail("TerminalProcessor should always throw an exception when reached");
           } catch (ArgumentException){}
    }
}

This may look a bit odd, but we’ll talk about exceptions and testing in another entry.  For now we think you can see that this test will pass if the exception is thrown if it is reached, and fail if it is not.

Oh, and don’t forget to specify your public constants!

[TestClass]
public class ConstantSpecificationTest
{
    [TestMethod]
    public void SpecifyConstants()
    {
           Assert.AreEqual(1, Processor.MIN_SMALL_VALUE);
           Assert.AreEqual(10000, Processor.MAX_SMALL_VALUE);
           Assert.AreEqual(10001, Processor.MIN_LARGE_VALUE);
           Assert.AreEqual(20000, Processor.MAX_LARGE_VALUE);
    }
}

In the next part, we’ll examine the third set of issues that have to do with the composition of the chain itself... that all the required elements are there, and that they are in the proper order (in cases where is important).  This will present us with an opportunity to discuss object factories, and how to test/specify them.

Stay tuned!

-----

[1] It’s important to note that patterns are not implementations.  We know many other forms of this pattern, but in this section will focus on the implementation shown in the Gang of Four.

[2] Unfamiliar with the Template Method Pattern?  We have a write up of it here:
http://www.netobjectives.net/patternrepository/index.php?title=TheTemplateMethodPattern

[3] Details on the use of an “Any” class is a subject into itself.  For now, just know that Any.Value returns a random integer, while Any.Value(min, max) returns a random integer within a range.

No comments:

Post a Comment