Chain Composition Behaviors
We always design services for multiple clients. Even if a service (like the Processor service in our example) has only a single client today, we want to allow for multiple clients in the future. In fact, we want to promote this; any effort expended to create a service will return increasing value when multiple clients end up using it.
So, one thing we definitely want to do is to limit/reduce the coupling from the clients’ point of view. The run-time view of the CoR from the client’s point of view should be extremely limited:
Note that the reality, on the right, is hidden from the client, on the left. This means we can add more processors, remove existing ones, change the order of them, change the rules of the termination of the chain, change how any/all of the rules are implemented... and when we do, this requires no maintenance on the clients. This is especially important if there are (or will be, or may be) clients that we don’t even control. Maybe they live in code belonging to someone else.
The one place where reality cannot be concealed is wherever the chain objects are instantiated. The concrete types, the fact that this is a linked list, and the current order of the list will be revealed to the entity that creates the service. If this is done in the client objects, then they all will have this information (it will be redundant). Also, there is no guarantee that any given client will build the service correctly; there is no enforcement of the rules of its construction.
This obviously leads us to prefer another option. We may, for example, decide to move all creation issues into a separate factory object.
It may initially seem that by doing so we’re just moving the problem elsewhere, essentially sweeping it under the rug. The advantage comes from the fact that factory objects, unlike clients, do not tend to increase in number. So, at least we’ve limited our maintenance to one place. Also, if factories are only factories then we are not intermixing client behavior and construction behavior. This results in simpler code in the factories, which tends to be easier to maintain. Finally, if all clients use the factory to create the service, then we know (if the factory works properly) that the service is always built correctly.
We call this the separation of use from creation, and it turns out to be a pretty important thing to focus on. Here, this would lead us to create a ProcessorFactory that all clients can use to obtain the service, and then use it blindly. Initially, this might seem like a very simple thing to do:
public class ProcessorFactory {
public Processor GetProcessor() {
return new LargeValueProcessor(
new SmallValueProcessor(
new TerminalProcessor()));
}}
Pretty darned simple. From the clients’ perspective, the issue to specify in a test is also very straightforward: I get the right type from the factory:
[TestClass]
public class ProcessorFactoryTest {
[TestMethod]
public void TestFactoryReturnsProperType() {
Processor processor =
new ProcessorFactory().GetProcessor();
Assert.IsTrue(processor is Processor);
}
}
This test represents the requirement from the point of view of any client object. Conceptually it tells the tale, though in strongly-typed language we might not want to actually write it. This is something the compiler enforces, and therefore is a test that actually could never fail if it compiles. Your mileage may vary.
However, there is another perspective, with different requirements that must also be specified. In TDD, we need to specify in tests:
- Which processors are included in the chain (how many and their types)
- The order that they are placed into the chain (sometimes) [4]
Now that the rules of construction are in one place (which is good) this also means that we must specify that it works as it should, given that all clients will now depend on this correctness.
However, when we try to specify the chain composition in this way we run into a challenge: since we have strongly encapsulated all the details, we have also hidden them from the test. We often encounter this in TDD; encapsulation, which is good, gets in the way of specification through tests.
Here is another use for mocks. However, in this case we are going to use them not simply to break dependencies but rather to “spy” on the internal aspects of an otherwise well-encapsulated design. Knowing how to do this yields a huge advantage: it allows us to enjoy the benefits of strong encapsulation without giving up the equally important benefits of a completely automated specification and test suite.
This can seem a little tricky at first so we’ll go slow here, step by step. Once you get the idea, however, it’s actually quite straightforward and a great thing to know how to do.
Step 1: Create internal separation in the factory
Let’s refactor the factory just a little bit. We’re going to pull each object creation statement (new x()) into its own helper method. This is very simple, and in fact most modern IDEs will do it for you; highlight the code, right-click > refactor > extract method..
public class ProcessorFactory {
public Processor GetProcessor() {
return MakeFirstProcessor(
MakeSecondProcessor(
MakeLastProcessor()));
}protected virtual Processor MakeFirstProcessor(
Processor aProcessor) {
return new LargeValueProcessor(aProcessor);}
protected virtual Processor MakeSecondProcessor(
Processor aProcessor) {
return new SmallValueProcessor(aProcessor);}
protected virtual Processor MakeLastProcessor() {
return new TerminalProcessor();
}
}
Note that these helper method would almost certainly be made private by an automated refactoring tool. We’ll have to change them to protected virtual (or just protected in a language like Java where methods are virtual by default) for our purposes. You’ll see why.
Step 2: Subclass the factory to return mocks from the helper methods
This is another example of the endo testing technique we examined in our section on dependency injection:
private class TestableProcessorFactory : ProcessorFactory {
protected override Processor MakeFirstProcessor(
Processor aProcessor) {
return new LoggingMockProcessor(
typeof(LargeValueProcessor), aProcessor);
}protected override Processor MakeSecondProcessor(
Processor aProcessor) {
return new LoggingMockProcessor(
typeof(SmallValueProcessor), aProcessor);
}protected override Processor MakeLastProcessor() {
LoggingMockProcessor mock = new LoggingMockProcessor(
typeof(TerminalProcessor), null)
mock.iElect = true;
return mock;}
}
This would almost certainly be a private inner class of the test. If you look closely you’ll see three important details.
- Each helper method is returning an instance of the same type (which we’ll implement next), LoggingMockProcessor, but in each case the mock is given a different type to specify in its constructor [5]
- The presence of the aProcessor parameter in each method specifies the chaining behavior of the factory (which is what we will observe behaviorally through the mocks)
- The MakeLastProcessor() conditions the mock to elect. As you’ll see, these mocks do not elect by default (causing the entire chain to be traversed) but the last one must, to specify the end of delegation
Step 3: Create a logging mock object and a log object to track the chain from within
Here is the code for the mock:
private class LoggingMockProcessor : Processor {
private readonly Type mytype;
public static readonly Log log = new Log();
public bool iElect = false;
public LoggingMockProcessor (Type processorType,
Processor nextProcessor):base(nextProcessor) {
mytype = processorType;}
protected override bool ShouldProcess(int value) {
log.Add(mytype);
return iElect;
}
protected override int ProcessThis(int value) {
return 0;
}
}
The key behavior here is the implementation of ShouldProcess() to add a reference of the actual type this mock represents to a logging object. This is the critical part -- when the chain of mocks is asked to process, each mock will record that it was reached, the type it represents, and we can also capture the order in which they are reached if we care about that.
The implementation of ProcessThis() is trivial because we are only interested in the chain’s composition, not its behavior. We’ve already fully specified the behaviors in previous tests, and each test should be as unique as possible.
Also note that this mock, as it is only needed here, should be a private inner class of the test. Because the two issues inclusion and sequence are part of the same behavior (creation), everything will be specified in a single test.
The Log, also a private inner class of the test, looks something like this:
private class Log {
private List<Type> myList;
public void Reset() {
myList = new List<Type>();
}
public void Add(Type t) {
myList.Add(t);
}
public void AssertSize(int expectedSize) {
Assert.AreEqual(expectedSize, myList.Count);
}
public void AssertAtPosition(Type expected, int position) {
Assert.AreEqual(expected, myList[position]);
}
}
It’s just a simple encapsulated list, but note that it contains two custom assertions. This is preferred because it allows us to keep our test focused on the issues it is specifying, and not on the details of “how we know”. It makes the specification more readable, and easier to change.
(A detail: The log is “resettable” because it is held statically by the mock. This is done to make it easy for all the mock instances to write to the same log that the test will subsequently read. There are other way to do this, of course, but this way involves the least infrastructure. Since the log and the mock are private inner classes of the test, this static member represents very little danger of unintended coupling.)
Step 4: Use the “spying” capability of the mock in a specification of the chain composition
Let’s look at the test itself:
[TestMethod]
public void TestFactoryReturnsProperChainOfProcessors() {
// Setup
ProcessorFactory factory = new TestableProcessorFactory();
const int correctChainLength = 3;
List<Type> correctCollection =
new List<Type> {
typeof (LargeValueProcessor),
typeof (SmallValueProcessor),typeof (TerminalProcessor)
};
Processor processorChain = factory.GetProcessor();
Log myLog = LoggingMockProcessor.log;
myLog.Reset();
// Trigger
processorChain.Process(Any.Value);
// Verification
myLog.AssertSize(correctChainLength);
for (int i = 0; i < correctCollection.Count; i++) {
myLog.AssertAtPosition(correctCollection[i], i);}
}
If the order of the processors was not important, we would simply change the way the log reports their inclusion:
// In Log
public void AssertContains(Type expected){
Assert.IsTrue(myList.Contains(expected));
}
...and call this from the test instead.
// In TestFactoryReturnsProperChainOfProcessors()
for (int i = 0; i < correctCollection.Count; i++) {
myLog.AssertContains(correctCollection[i]);
}
Some testing frameworks actually provide special Asserts for collections like this.
Objections
OK, we know what some of you are thinking. “Guys, this is the code you’re testing:”
public Processor GetProcessor() {
return MakeFirstProcessor(
MakeSecondProcessor(
MakeLastProcessor()));
}“...and look at all the *stuff* you’ve created to do so! Your test is several times the size of the thing you’re testing! Arrrrrrrrrgh!”
This is a completely understandable objection, and one we’ve felt in the past. But to begin with remember that in our view this is not a test, it is a specification. It’s not that unusual for specifications to be longer than the code they specify. Sometimes it’s the other way around. It just depends on the nature of the specification and the implementation involved.
The specification of the way the space shuttle opened the cargo bay doors was probably a book. The computer code that opened it was likely much shorter.
Also, this is a reflection of the relative value of each thing. Recently, a friend who runs a large development team got a call in the middle of the night, warning him of a major failure in their server farm involving both development and test servers. He knew all was well since they have offsite backups, but as he was driving into work in the wee hours he had time to ask himself “if I lost something here... would I rather lose our product code, or our tests?”
He realized he would rather lose the product code. Re-creating the source from the tests seemed like a lot less work than the opposite (that would certainly be true here). But what that really means is that the test/specifications actually have more irreplaceable value than the product code does.
In TDD, the tests are part of the project. We create and maintain them just like we do the product code. Everything we do must produce value... and that’s the point, not whether one part of the system is larger than another. And while TDD style tests do certainly take time and effort to write, remember that they have persistent value because they can be automatically verified later.
Finally, ask yourself what you would do here if the system needed to be changed, say, to support small, medium, and large values? We would test-drive the new MediumValueProcessor, and then change TestFactoryReturnsProperChainOfProcessors() and watch it fail. We’d then update the factory, and watch the failing test go green. We’d also have automatic confirmation that all other tests remained green throughout.
That’s an awfully nice way to change a system. We know exactly what to do, and we have concrete confirmation that we did exactly and only that. Such confidence is hard to get in our business!
-----
Links:
http://www.netobjectives.com/competencies/separate-use-from-construction
http://www.netobjectives.com/resources/separate-use-construction
-----
[4] Some CoRs require their chain elements to be in a specific order. Some do not. For example, we would not want the TerminalProcessor to be anywhere but at the end of the chain. So, while we may not always care about/need to specify this issue, it’s important to know how to do it. So we’ll assume here that, for whatever domain reason, LargeValueProcessor must be first, SmallValueProcessor must be second, and TerminalProcessor must be third.
[5] We’re using the class objects of the actual types. You could use anything unique: strings with the classnames, an enumeration, even just constant values. We like the class objects because we already have them. Less work!