Net Objectives

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

Tuesday, October 21, 2014

TDD and Asychronous Behavior: Part 1


In TDD we write tests as specifications where each of them is focused on a single behavior of the system.  A “good” test in TDD makes a single, unique distinction about the system.  

But this means when the TDD process is complete our spec also serves as a suite of tests against regression.  This is a hugely valuable side-effect of TDD.  We don’t write “tests” but we get tests too, and with no additional effort.  As tests we also want each of them to be unique in another sense: each has only one reason to fail.  Thus when a test fails we will know exactly why.

We say it this way:  “A given test tests everything which is in scope, but which the test does not control.”  Clearly we never want to test “everything in scope” but rather one unique thing at a time.  The implication is that everything other than that one thing must be controlled by the test.

This can include many things… framework objects, libraries, the user interface, the database, time, randomness, devices and sensors, the network, etc…  For many of these entities we can solve the problem with the endo-testing technique [1], but one aspect of development can pose a special problem: multi-threaded execution.

Mutex

When a given behavior safely supports multiple threads it does so either because it is stateless/re-entrant, or it is using some mechanism to ensure mutual exclusion (“mutex”).  If the object is stateless or re-entrant then there are no thread-related issues to deal with in a test.  But if it is ensuring mutex as well then the mechanism it employs to do so is “in scope.”  When we are specifying the behavior of the object we don’t want to also specify the mutex-ensuring behavior in the same test.  Thus, we have to bring it under the control of the test.  This actually isn’t that hard as it might seem.  It’s a matter of technique.

Our first step is to separate the mutex behavior from the primary responsibility of the object.

Often objects will use thread locks in order to protect some functionality from being accessed by multiple threads simultaneously.  The problem is that these objects would be doing two things: providing the core functionality and managing the locks.  A given object should not be responsible for two things.  That’s a basic tenet of good design we call cohesion.  So the first step is to separate the two responsibilities, and one way to do this is by using a Synchronization Proxy.

The Synchronization Proxy

If we use a Synchronization Proxy [2] we can separate these two responsibilities into two different objects.  This means that the primary object will now only provide its core functionality (meaning we can specify its behavior it in a straightforward, single-threaded way) and the Synchronization Proxy will ensure the mutex.  We’ll explain the proxy first, and then of course we have to discuss how to specify its behavior in its own test.

Because we want to focus on techniques for specifying/testing the proxy part of this, the main object (which we are calling Target) will have an extremely simple behavior.  It has a bit of state that can be changed by calling a “Set” method.

public class Target {
    public int x;
    public override void SetX(int xValue) { x = xValue; }
}

Obviously this is a trivial class, and easy to specify in a test.  Remember, we’re not concerning ourselves with this class, but rather the proxy’s behavior that is going to ensure that two threads cannot call SetX() simultaneously or in an overlapping way.  First, we create an abstraction for this Target:

public abstract class Target {
    public abstract void SetX(int xValue);
}

public class RealTarget: Target {
    public int x;
    public override void SetX(int xValue) { x = xValue; }
}

Now RealTarget is an implementation of the Target abstraction.  Any client object will see only Target.  In a single-threaded system it could use RealTarget directly, but if multiple threads are to be supported we need to do more.  In that case we create a second implementation, which is the Synchronization Proxy.

public class SimpleLockSynchronizationProxy : Target {
    private RealTarget myTarget;
    public SimpleLockSynchronizationProxy(RealTarget aTarget) { myTarget = aTarget; }
    public override void SetX(int xValue) {
        lock (this) {
            myTarget.SetX(xValue);
        }
    }
}



Note that this proxy takes an instance of RealTarget it its constructor, and delegates to it for the core behavior (SetX()) which would be specified it its own (trivial) test. But the proxy takes the lock along the way, and thus guards against multiple, simultaneous, overlapping accesses. The proxy will not allow a thread to enter SetX() if another thread is currently executing it.

At run-time, we “string them together” and expose the Proxy to client objects, up-cast to Target. Note this make no difference to any client code. The question, therefore, is how we drive/specify/test that the proxy is, in fact, preventing the multiple access problem from occurring.

In part 2 we will deal with this issue.

...to be continued...


[1] Endo-Testing will be covered in a future blog
[2] If you are not familiar with the Proxy Pattern, pay a visit here: http://www.netobjectives.com/PatternRepository/index.php?title=TheProxyPattern
 

No comments:

Post a Comment