Saturday, August 15, 2009

Unit tests and the Layers Pattern (part 1)

In previous postings I talked about techniques that I use when adding unit tests to legacy code. The two techniques were Introduce middle man and Extract static method. I admit that those techniques may not sound very impressive at first. However, they did help me to add unit tests to legacy code on a number of occasions where it would have been difficult otherwise. Adding tests to code late in the development (or in a later version of the software) is quite a challenge. Ideally you want to add unit tests early in the development. In fact you want to design your code for testing.
I should point out that on my blog when I talk about unit tests I mean automated unit tests that can run as part of the build process on any machine. When I talk about the other types of unit tests I will use an expression like manual unit tests on some other more precise expression depending on the exact type of tests..
Today I will talk about one of the most powerful design technique I have found for writing code to be unit tested. The technique is organized around an architectural Pattern calledLayers.
Anyone familiar with networking theory knows about the OSI layers. The OSI model is one good example of this pattern even though sometimes the actual layers implemented in the real world do not exactly match the theory. If you are not familiar with the OSI model it's not a problem because the actual example that I am going to present is not very complicated and should allow you to understand the pattern. The Layers pattern is presented in the excellant book A system of pattern by Bushmann, Meunier, Rohnert, Sommerlad and Stal:
The Layers pattern helps to structure applications that can be decomposed into groups of subtasks in which each group of subtasks is at a particular level of abstraction.

The Pattern is particularly useful when building modules that access hardware (device drivers) or that communicate with server or other external entity. In the case of a device driver for example you could split the module into three layers (I show the abbreviations that I will use later for those layers in parentheses):

Interface (IL)
Logical (LL)
Low-level access (LLAL)

The responsibilities are divided as:

  1. Interface Layer (IL): defines the methods and classes used to interact with the module from a client application.

  2. Logical Layer (LL): implements the functions defined in the interface layer using the data supplied by the low-level layer. Also uses the low-level layer to request additional information when needed. It is the layer where most of the complicated business logic is located.

  3. Low-level access Layer (LLAL): handles the low-level details. This can be communicating with the hardware, communication with the server or other low-level operations.

There are other benefits to using the layers Pattern but here we will focus on how it can benefit unit tests.

  • Often the problem with unit testing client/server or code that interfaces with hardware is that it is difficult to have the setup on all machine that needs to run the automated unit tests. Layers can help mitigate this by separating the part that interfaces with the hardware or server from the rest of the code.

  • Also sometimes it is difficult to test specific scenario when using the actual hardware or when interacting with a server.

In both cases, the Layers pattern can help by making the building and use of mock implementation easier. Having a simple well defined low-level layer is a key ingredient here.
Before we dive into an example lets consider some of the pitfalls that you might encounter when using Layers:

Communication overhead between layers

You have to watch out for this and if needed make compromise in order to get acceptable communication costs. You may need to transfer some responsibilities between layers if things degenerate.

Coupling between layers

You want to avoid static circular dependencies. In a clean design the dependencies will look something like:

|IL| /__ |LL| __\ |LLAL|

The dependencies are from LL to the other layers. Again, you may need to compromise and/or add packages (namespaces) to break circular dependencies. Sometimes simply adding an interface to a layer will do the trick (invert the dependency). In practice you will often end-up with something like:

/ \ / \
|IL| /__ |LL| __\ |LLAL|

The extra dependencies here are all towards package-1 or package-2. Of course you can have additional packages (or namespaces) inside each layers.

Next time we will look at a nice example.

No comments:

Post a Comment