Chapter 6: Dummy and Mock Objects for Independence


In an averagely complex application, an object can hardly do without the cooperation of many other objects of the same or another class. So how can we test an object that depends on so many others? The most pragmatic and (probably) intuitive way out of this dilemma is the bottom-up approach: we begin our development and testing with the classes that build themselves on system-owned classes. Then we use these tested components to build independent classes.

This approach is called bottom-up because we start from the very bottom, with the most concrete objects, working our way up to the more abstract components. We saw in Chapter 3, Section 3.2 that working top-down (or outside-in) is easier in the test-first approach. In that section, we also identified problems caused by the dependence of various classes upon each other and external sources. This chapter shows how to avoid these dependencies or subsequently eliminate them in many cases.

6.1 Little Dummies

One important unit-testing rule comes up over and over again: a single test case should be as local as possible; that is, it should test only the object we are currently dealing with and not all the others it requires for its job or with whom it cooperates and to which it delegates tasks. One way to come a step closer to the goal of maximum independence of a test is through the use of dummy objects. This means that we replace part of our objects by others that are only pretending. A simple example will explain this idea.

We want to program a Euro calculator that converts and returns a given amount of currency into Euros; [1] an instance of class ExchangeRateProvider shall be used for retrieving the actual exchange rates. Naturally, we first do the tests:

 public class EuroCalculatorTest extends TestCase {    public void testEUR2EUR() {       double result = new EuroCalculator().valueInEuro(1.0, "EUR");       assertEquals(1.0, result, 0.00001);    }    public void testUSD2EUR() {       double result = new EuroCalculator().valueInEuro(1.0, "USD");       assertEquals(1.1324, result, 0.00001);    } } 

We use the class ExchangeRateProvider as a readily available component; it provides the rate of exchange for all common currencies over a network connection:

 public class ExchangeRateProvider {    public double getRateFromTo(String fromCurrency, String to) {       double retrievedRate = ... // Access to server over network       return retrievedRate;    } } 

Although implementing the EuroCalculator class is easy, our tests still have a few problems. First, the testUSD2EUR() test case will probably function only for a short time, namely as long as the exchange rate between Dollar and Euro does not change. Second, accessing the exchange rate server is a very insecure matter, because we access it over a network. The server may have long response times or even be unavailable for an unknown time due to overload. This dependence upon an external service can mean that our test may go wrong, even though our program does not have any error.

One way to solve this problem is the use of a fake exchange rate server to which we can give the expected exchange rate right away:

 public class DummyProvider extends ExchangeRateProvider {    private double dummyRate;    public DummyProvider(double dummyRate) {       this.dummyRate = dummyRate;    }    public double getRateFromTo(String from, String to) {       return dummyRate;    } } 

Now we have to find a way to smuggle our dummy into EuroCalculator. One option is to expand the signature of the valueInEuro() method by a parameter of the ExchangeRateProvider type. This is one possible design decision which requires that all clients of EuroCalculator have an instance of ExchangeRateProvider available; if necessary, this decision can be changed later on (see Chapter 6, Section 6.8). Anyway, we seize this opportunity to drag the expected accuracy to a constant, ACCURACY. The modified test looks like the following:

 public class EuroCalculatorTest extends TestCase {    private final static double ACCURACY = 0.00001;    public void testEUR2EUR() {       ExchangeRateProvider provider = new DummyProvider(1.0);       double result = new EuroCalculator().          valueInEuro(2.0, "EUR", provider);       assertEquals(2.0, result, ACCURACY);    }    public void testUSD2EUR() {       ExchangeRateProvider provider = new DummyProvider(1.1324);       double result = new EuroCalculator().          valueInEuro(1.5, "USD", provider);       assertEquals(1.6986, result, ACCURACY);    } } 

And here we are: our test runs fast and stable and it is independent of fluctuating exchange rates. But we have to be aware of the fact that we are testing only the EuroCalculator class and not the exchange rate server. We assume that the exchange rate server was (hopefully) tested by the provider of the component. If we are the provider, then we also test the server, but in another test suite.

[1]To simplify things, we use the primitive type double to represent monetary amounts, but this primitive type is rarely used in the real world due to its rounding problem.




Unit Testing in Java. How Tests Drive the Code
Unit Testing in Java: How Tests Drive the Code (The Morgan Kaufmann Series in Software Engineering and Programming)
ISBN: 1558608680
EAN: 2147483647
Year: 2003
Pages: 144
Authors: Johannes Link

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net