Project Management

Testing the Chain of Responsibility, Part 1

From the Sustainable Test-Driven Development Blog
by
Test-driven development is a very powerful technique for analyzing, designing, and testing quality software. However, if done incorrectly, TDD can incur massive maintenance costs as the test suite grows large. This is such a common problem that it has led some to conclude that TDD is not sustainable over the long haul. This does not have to be true. It's all about what you think TDD is, and how you do it. This blog is all about the issues that arise when TDD is done poorly—and how to avoid them.

About this Blog

RSS

Recent Posts

ATDD and TDD

The Importance of Test Failure

Mock Objects, Part 1

Mock Objects, Part 2

Mock Objects, Part 3



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 Design Patterns 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?

Posted on: February 11, 2021 08:07 AM | Permalink

Comments (0)

Please login or join to subscribe to this item


Please Login/Register to leave a comment.

ADVERTISEMENTS

The second day of a diet is always easier than the first. By the second day you're off it.

- Jackie Gleason