Net Objectives

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

Monday, July 2, 2012

Testing the Chain of Responsibility, Part 1

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:

Here’s the UML at it appears in the Gang of Four book [1]:

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

Individual Handler Behavior
  1. That a given handler will choose to act (elect) when it should
  2. That a given handler will not elect when it shouldn't
  3. That upon acting, the given handler will perform its function correctly

Chain Traversal Behavior
  1. That a handler which elects itself will not delegate to the next handler
  2. That a handler which does not elect itself will delegate to the next handler, and will hand it the parameter(s)  unchanged
  3. That a handler which does not elect will “hand up” (return) any result returned to it without changing the result

Chain Composition Behavior
  1. The chain is made up of the right handlers
  2. The handlers are given “a chance” in the right order

The first two sets of  behaviors can be specified using a single, simple mock object:

The same mock can be used to test each handler.  If the mock is conditionable and inspectable, it can be used to test all five scenarios.

Example mock code (pseudo-code) [2]:

class Mock: TargetAbstraction {
    private bool wasCalled = false;
    private par passedParam;
    private ret returnValue;

    public ret m(par param) {
        passedParam = param;
        wasCalled = true;
        return returnValue;
    }

      // This makes the mock inspectable.  
      // The test can tell if the mock was called.
    public boolean gotCalled() {
        return wasCalled;
    }

      // This also makes the mock inspectable.  
      // The test can check to see what it received.
    public par passedParameter() {
        return passedParam;
    }

      // This makes the mock conditionable.  
      // The test can dictate what it returns to the tested handler.
    public void setReturn(ret value){
        returnValue = value;
    }
}  

The same mock can be used to test each Handler because:
  1. The mock can report whether it got called or not.  This allows us to use it for both the scenario where it should have been called and the scenario where it should not have been.
  2. The mock can report what was passed to it.  This allow us to use it in the scenario when a given Handler, not electing, passes the data along “unmolested” to the next Handler (in this case, the mock).
  3. The mock can be “programmed“ to return a known value.  This allows us to use it in the scenario where a given Handler, not electing, should bubble up the return from the next Handler unmolested to the caller.

However, if we started to write these tests for each and every handler, we would find that the tests were almost entirely duplications of each other.  The set of tests for each handler would do precisely the same things (albeit for a different handler implementation) except for the one test which specified “That upon acting, the given handler will perform its function correctly”.  We do not like to write redundant tests any more than we like to write redundant production code.

This, in other words, causes “pain” in the tests and, as we say frequently, all pain is diagnostic.  Perhaps the problem is in the implementation.  

The Chain of Responsibility, as classically implemented, does put two different responsibilities into each handler: to select whether it should behave, or not, and what to do in each case.

The Chain Composition, however, initially seems tricky to test.  The pattern does not specify where and how the chain is created, which is typical with patterns; we can pair the CoR with any one of a number of creational patterns, depending on how this issue should be handled, the nature of the rules of creation, how dynamic the chain needs to be, and so forth.

The short answer is that the creation of the chain should be done in a factory class.  We will deal with these issues, and the redesign they suggest, in part 2.  

However, this might be a fun challenge for you.

How would you test (really, specify) the creation of a Chain of Responsibility implementation?  Remember you need to specify in a test that the factory includes all the necessary chain objects when it builds the collection, and that the objects are in the chain in the proper order.  Give it a try, and post your thoughts in the comments section here.

---------

[1] “Design Patterns, Elements of Reusable Object Oriented Software” Gamma, Helm, Johnson, Vlissides

[2] Do we really need to encapsulate our mocks so completely?  Here Scott and Amir are not (yet) in agreement.  Amir says we can do this much more simply:

class Mock : TargetAbstraction {

    public bool gotCalled = false;
    public par passedParam;
    public ret returnValue;

    public ret m(par param) {
        passedParam = param;
        called = true;
        return returnedRet;
    }
}

Scott feels that public state is a bad smell to be avoided in all code.  So, either Amir will beat Scott into submission, or vice-versa.  What do you think?

6 comments:

  1. Every time I need to use a CoR, I implement the "passing along" mechanism in a separate class. That class also implements the Handler interface and usually holds a reference to a list of other Handlers, in the correct order. That makes for a much better allocation of responsibilities, avoiding duplication and, as a happy consequence, increasing testability.

    As for the question: "are the handlers arranged in the correct order?": usually, the "master handler" (the one that delegates calls to the other handlers) is created by a factory or it has the handlers list built into itself (i.e. hardcoded). Either way, it's just a list. The implementation itself documents how the handlers are arranged. So, there's no need for a unit test to verify that.

    ReplyDelete
  2. I've used the controller approach too... it is one way to avoid duplication. Another way is to make the base abstraction an actual Abstract Class, and use a template method for the "passing along" mechanism.

    Also agree about the factory approach, except where you say "it's just a list. The implementation itself documents how the handlers are arranged." For us, the test suite should form a complete specification, and should provide complete coverage when run against the code. So, the number of handlers, the order they are placed into the collection, etc... needs to have a test driving it. We'll show some ideas for doing that in part 2.

    ReplyDelete
  3. About the question on specifying the chain of responsibility creation - maybe that's a bit overcomplicated, but I'd try to use a builder inside a factory and
    1. in the factory spec: specify whether correct calls are made on the builder.
    2. in the builder spec - verify each build step separately.

    It's just a quick thought, I'm not really sure whether it would work fine.

    ReplyDelete
    Replies
    1. We're finishing up part 2 of this blog this week, and yes, our solution is along the lines you are suggesting. Stay tuned!

      Delete
    2. By the way, I just noticed how hard it is to get rid of the "testing" language and thinking by reading my own comment.

      "in the factory spec: specify whether correct calls are made on the builder" should actually be something like "in the factory spec: specify what commands should the builder receive to create the chain we need."

      "in the builder spec - verify each build step separately." should probably be something like "in the builder spec - specify how should each building step behave".

      Funny how hard it is to switch to the "specification" thinking :-).

      Delete
  4. I'd like to reiterate what Scott said vis-a-vis "testing" the list of handlers. Someone needs to decide which handlers are available and in which order they are to be called. This is not an implementation decision - it's a specification; as such it requires a testification(c).

    ReplyDelete