In part 1, we discussed the benefits of separating out the code that ensures mutex (in this case, using thread locks) from the code that provides core behavior using a Synchronization Proxy. The core behavior can be tested in a straightforward, single-threaded way. What remains in terms of TDD and asynchronous behavior is how to effectively specify/test the Synchronization Proxy.
Testing the Synchronization Proxy
You might be saying “the proxy class is so simple, I’m not sure I’d need to drive its behavior from a specification/test. All it does is take the lock and delegate” The level of rigor in your specifications is always a judgment call, so we’ll set aside whether a given proxy behavior needs a test. We’re going to focus on how to write such a test it in the case where you wish to. In other words: if you decide not to include it in the specification we want it to be because you decided not to, not because you didn’t know how.
The given-when-then layout of the specification would be something along these lines:
Threads A and B are running
Thread A is running code T
Thread B attempts to run code T
Thread B will wait until Thread A is done: the accesses with be serial, not parallel.
The key here is the word “until”. What the test needs to drive/specify/ensure is that the timing is right, that Thread B writes *after* Thread A even if Thread A takes a long time. Let’s look at an implementation sequence diagram.
Client A and Client B are inner classes of the test, created just to exercise the proxy, each in its own thread. If the proxy did not add the synchronization behavior, the writes to Target would be 2, and then 1, because we tell the Target to wait 10 seconds before writing the state for Client A, but only 1 second for Client B. If the proxy prevents this (proper behavior) then the writes will be 1, and then 2, because Client B couldn’t get the access until Client A was finished.
This is a partial solution, but it begs a few questions.
- How does the test get RealTarget to wait these different amounts of time?
- How does the test assert the sequence of these writes is 1, 2?
- If the RealTarget “waits 10 seconds” won’t the test execution be horribly slow?
What about the time issue? Well, time is in scope and we certainly are not testing that time works. So we have to control it in the test as well.
Here’s the implementation sequence diagram with the mock object in place of RealTarget, and another object that replaces time.
Time is a simulator, which can be told by the test to “be” at any time we want. MockTarget basically calls on Time and says “let me know when we’ve reached or passed second x”. We use the Observer Pattern  to implement this. The first time the mock is called it will ask Time to notify it when 10 seconds have passed. The second time, it will ask for a 1 second notification. We do this with a simple conditional.
Furthermore MockTarget maintains a log of all calls made to it, in order, which the test can ask for to determine the sequence of setX() calls and assert that it is 1, 2 rather than 2, 1.
10 and 1 are not significant numbers, so as you’ll see in the code we made constants “longWait” and “shortWait” to be used in the test. It’s only important that the first thread waits longer than the second, and since time itself is being simulated anyway the “actual” lengths of time are unimportant. We can pretend they are one year and one hundred years if you want. It’s nice to control time. :)
MockTarget, Time, and the ClientA and ClientB objects are all part of the test, and so a good practice is to make them private inner classes of the test. Also the Observer interface and all constants used in this test are similarly part of the test itself. Remember, a test tests everything which is in scope but which the test does not control. The only thing not controlled by the test is the Synchronization Proxy.
We’ve coded all this up in C#. Click here to download the visual studio project.
 Perhaps later we’ll make our argument about whether you should or not. :)
 If you don't know about the Mock Object Pattern, visit this link:
 For more details on the Observer Pattern, visit this link:
Post a Comment