Download the podcast.
Techniques
There are many ways to create a mock object by hand. You will likely come up with your own techniques, which may make use of language elements and idioms made possible by the particular languages and frameworks you work with. It is important to know more than one technique because under various circumstances during the development process we are able and unable to change different things. Also, we may be dependent on the work of other teams or organizations who might not have created an ideal situation for us if we seek to write the kinds of tests this book is about.
For example, let’s say the group who implemented the GPS system did not create a separate interface for their implementation, but instead just created a concrete API object that gives us access to the hardware:
No Interface for the GPS dependency |
I suppose we could take our testing laptop out into the field, hook it up to the real global positioning hardware, and physically move the system around the field, running the test in different locations. At some point, no doubt, such testing will take place. But remember, in TDD we are not really testing, we are specifying, and the spec should be useful and runnable at any point in time, under any circumstances. So, we must mock the GPS class.
Let’s start simply:
Direct Inheritance |
Here we have simply sub-classed the “real” GPS class. As was the case when GPS was an interface, our mock will up-cast and appear (to BoundaryAlarm) to be the real thing. The extra method SetCurrentLocation() will be available to the test, for conditioning purposes, because the test will hold the reference to the mock in a down-cast to its actual type.
But this technique may not work, or may product negative effects.
- If GPS is not able to be sub-classed (it is final, or sealed, or whatever your language would call a class that cannot be sub-classed), then this is obviously not possible.
- If the language you are using has both virtual and non-virtual methods, the GetCurrentLocation() method must be a virtual method, otherwise the up-cast of the mock will cause the original method to actually be called, rather than the mock’s method, and the test will not work.
- In sub-classing, you create strong coupling between the mock and the original class. One effect of this is the fact that when the test creates an instance of the mock (using new MockGPS()), the class loader will also create an instance of the base class (GPS) in the background. It must do this, as the original implementation methods are available to the sub-class (via Base() or super() or a similar method, or by direct access). If merely creating an instance of GPS is a disadvantage (it slows down the test, or requires that the actual hardware must be present when the test runs, etc…) then sub-classing like this is something we don’t want.
We could wrap the interface in an adapter (we’ll leave DashLight out of the discussion from this point forward):
Wrapping the dependency |
GPSWrapper is our class, which we created just for this purpose, so we could mock it. Clearly we would have to change BoundaryAlarm in this case, as it no longer directly depends on GPS but rather on the GPSWrapper, and these two are not interchangeable. But, if we can change BoundaryAlarm and cannot change GPS, then this technique is appropriate.
Note that the adapter and the “real” GPS have the same method name in this example. This is for simplicity; we could have called the method in the adapter anything we chose. If the team implementing the real GPS chose a method name we disliked (maybe we think it is unclear, or overly generic) this is also an opportunity to change this, and to make our code more readable.
Note also that the adapter is kept extremely simple; we are not going to be able to test it in TDD (we will do so in integration testing, but those are not run frequently) and so we really don’t want it to have any kind of complex behavior that might fail. We’ll probably do something like this:
public GPSWrapper {
private GPS myGPS;
public GPSWrapper() {
myGPS = new GPS();
}
public virtual Location GetCurrentLocation() {
return myGPS.GetCurrentLocation();
}
}
There’s very little to this, and that’s intentional. Whenever we create object like this (to wrap any dependency, like the database, the UI, the network, whatever) we call them “periphery objects” because they live on the boundary between the system we are developing and are responsible for, and other systems that we seek to control by mocking. We obviously cannot write TDD-style tests for these objects, and so we always keep them as minimal as possible.
This solves most of the problems of direct inheritance. Even if GPS is sealed and has non-virtual methods, our wrapper does not have these problems since we can create it any way we like. However, note that the inheritance from the mock to the wrapper still means that the real wrapper will be instantiated at test time, and since the wrapper creates an instance of the original GPS object we may still find our test is too slow, or can only be run in the presence of the hardware, etc… If this is a problem, we can take this one step further, and create an interface for wrapping:
Interface for Wrapping |
This eliminates the inheritance coupling between the mock and the implementation entirely, and thus we create no instance of the actual wrapper implementation (GPSWrapperImp) at test time. Our tests will run fast, be completely repeatable, and will not require the actual GPS system to run.
These are all techniques for creating mocks by hand. Another approach is to use a mocking
framework, a tool that creates these mocks for you. We’ll examine an example of such a tool a bit
later on, and also discuss what we like and dislike about the use of such
tools. In any case, whether you automate
you mocks or write them by hand, every developer should know how to handcraft
them, how they work, and what they do.
Next, in part 3, we’ll deal with different ways of injecting the mock into the class under test. In these examples the issue was simple: the constructor of BoundaryAlarm takes its two dependencies as parameters, allowing the test to send in the mock instead of the actual object. But what if we didn’t have this? We need more techniques, and we’ll examine a few.
No comments:
Post a Comment