Testing Best Practices: Test Categories, Part 1
| Successfully adopting and practicing TDD in a sustainable way involves many distinctions, best-practices, caveats, and so forth. One way to make such information accessible is to put in into a categorized context. The Design Patterns, for instance, are often categorized into behavioral, structural, and creational.[1] Here we will do a similar thing with the executable specifications (“tests”) we write when doing TDD.
Static behavior is the same for all values of all parameters passed. For example, f() here takes a single parameter, while g() takes two. But for all values of these parameters, the behavior is the same and so we pick "any" values to demonstrate this.
Two examples.... f() with it's single parameter provides the same behavior for all values but one... the point indicated. With the two parameters g() takes, the singularity may involve them both, creating a point, or it may only pertain to one, creating a line. For instance, if x is "altitude" and y is "temperature" then a point might indicate "same behavior for all values except 3000 feet and 121 degrees. The line might indicate "the same behavior for all values except 2000 feet at any temperature".
Two points define the boundary where behavior changes, and we also demonstrate the epsilon (or atom) of change.
Two boundaries, with epsilons for each. Note the boundaries of a simple range are not related to each other. |
Testing the Chain of Responsibility, Part 2
| Chain Composition Behaviors 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. new SmallValueProcessor( new TerminalProcessor())); }
MakeSecondProcessor( MakeLastProcessor())); } Processor aProcessor) { return new LargeValueProcessor(aProcessor); Processor aProcessor) { return new SmallValueProcessor(aProcessor); Processor aProcessor) { return new LoggingMockProcessor( typeof(LargeValueProcessor), aProcessor); } Processor aProcessor) { return new LoggingMockProcessor( typeof(SmallValueProcessor), aProcessor); } typeof(TerminalProcessor), null) mock.iElect = true; return mock;
Processor nextProcessor):base(nextProcessor) { mytype = processorType; new List { typeof (LargeValueProcessor), typeof (SmallValueProcessor), // Trigger processorChain.Process(Any.Value);
for (int i = 0; i < correctCollection.Count; i++) { myLog.AssertAtPosition(correctCollection[i], i); MakeSecondProcessor( MakeLastProcessor())); } [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. |
Testing Through API's
| We recently got a question from Tomas Vykruta, a colleague of ours, and we felt it turned out to be such a good, fruitful question that we wanted to pass it, and our answers, along in this blog. Do you prefer to have unit tests written against the public API, or to test individual functions inside the API? I've seen both approaches at my company, and in many cases, a single class is unit tested with a mix of the two. I haven't seen this topic addressed in any style or testing guides, so it seems to be left as a choice to the author.
My view is this: if you consider the test suite as you would a specification of the system, then the question as to whether to test at one level or another becomes: “is it specified?”
Scott has already expanded on the difference between testing and specification. I would like to add a little to this ‘specification’ perspective. We would love to hear from all of you on this question! |
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. MUTEXWhen 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 PROXYIf 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); } } } [1] Endo-Testing will be covered in a future blog [2] If you are not familiar with the Proxy Pattern, pay a visit here: https://www.pmi.org/disciplined-agile/the-design-patterns-repository/the-proxy-pattern |
TDD and Asychronous Behavior: Part 2
| 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
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.
The first two questions are answered by replacing RealTarget with a Mock Object[4]. Remember, we are not specifying RealTarget here, we are specifying the proxy’s behavior, therefore RealTarget must be controlled by the test. A mock allows this. 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 [5] 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.
[3] Perhaps later we’ll make our argument about whether you should or not. :) https://www.pmi.org/disciplined-agile/the-design-patterns-repository/the-mock-object-pattern [5] For more details on the Observer Pattern, visit this link: https://www.pmi.org/disciplined-agile/the-design-patterns-repository/the-observer-pattern TDD and Asychronous Behavior: Part 1 TDD and Asychronous Behavior: Part 2
|












