Data Injection

The simplest form of override encapsulation allows you to inject simple data into the software under test. No matter how much you model actors in your system as objects, attributes are still the fundamental building blocks. Let’s start by using encapsulation and overriding with basic data types as shown in Listing 8-1.

Listing 8-1: Code under test for basic data type injection

public class TemperatureWatcher {
  public static final double REALLY_COLD_F = 0.0;
  public static final double REALLY_HOT_F = 100.0;

  public static final String REALLY_COLD_RESPONSE =
      "Really cold!";
  public static final String REALLY_HOT_RESPONSE =
      "Really hot!";
  public static final String NORMAL_RESPONSE = "Okay";

  private double readThermometerF() {
    // Read the thermometer
    return temperature;
  }

  public String describeTemperature() {
    double temperature = readThermometerF();
    if (temperature <= REALLY_COLD_F) {
      return REALLY_COLD_RESPONSE;
    }
    if (temperature >= REALLY_HOT_F) {
      return REALLY_HOT_RESPONSE;
    }
    return NORMAL_RESPONSE;
  }
}

This example demonstrates an interaction with the outside world, a perfect opportunity to use encapsulation to support testability. Interacting with the physical world includes a wide variety of interactions, like checking CPU load or memory utilization, taking voice or keyboard input, receiving data over the network, acquiring an image from a camera, or reading external sensors like thermometers or joint actuators.

With the encapsulation already done, we can adjust the visibility of readThermometerF() and use it as our testing seam by overriding it in our tests. We will vary our approach a little with this example. The mapping of the input to the output is easily specified as a pair of values, and we want to try several values in the range. We could write a test method for each data pair, but that would obscure that we are really executing the same test with different data. We could write a single test with a loop to iterate over the different data cases, but the loop really is not part of the test, just a mechanism we create to supplement the test framework. Instead, let’s use TestNG1 and its support for data-driven tests, sometimes referred to as table-driven tests (Listing 8-2).

1. JUnit has a similar feature that would be equally appropriate for our particular example, but I find it more cumbersome for most real-life examples. Look at the use of @RunWith(Parameterized.class) for details. While TestNG allows you to have the table defined per test method, JUnit forces you to define it per test class and runs the entire class for the parameters whether they use them or not.

Listing 8-2: Test for Listing 8-1 using override injection

public class TemperatureWatcherTest {
  @Test(dataProvider = "describeTemperatureData")
  public void testDescribeTemperature(
      double testTemp, String result) {
    TemperatureWatcher sut =
        new SoftTemperatureWatcher(testTemp);

    String actualResult = sut.describeTemperature();

    assertEquals(actualResult, result);
  }

  private Object[][] describeTemperatureData() {
    return {
      {TemperatureWatcher.REALLY_COLD_F - 10.0,
        TemperatureWatcher.REALLY_COLD_RESPONSE},
      {TemperatureWatcher.REALLY_COLD_F,
        TemperatureWatcher.REALLY_COLD_RESPONSE},
      {TemperatureWatcher.REALLY_COLD_F + 10.0,
        TemperatureWatcher.NORMAL_RESPONSE},
      {TemperatureWatcher.REALLY_HOT_F,
        TemperatureWatcher.REALLY_HOT_RESPONSE},
      {TemperatureWatcher.REALLY_HOT_F + 10.0,
        TemperatureWatcher.REALLY_HOT_RESPONSE}
    };
  }

  private class SoftTemperatureWatcher {
    private double temperature;
    public SoftTemperatureWatcher(double temperature) {
      this.temperature = temperature;
    }

  @Override
  protected double readThermometerF() {
    return temperature;
  }
}

The @Test annotation’s dataProvider parameter says to use the data provider named describeTemperatureData to drive the test method. TestNG will find either a method tagged with that data provider name or a method by that name. The data provider method returns an array of arrays of objects. The arrays of objects must be the size of the parameter list for the test method and the data must be of assignment-compatible types.2 Our data requires the temperature we want the thermometer to return and the expected response. Our data provider defines a range of interesting values, all expressed relative to the constants defined in the class.

2. In Java, this pretty much means of or derived from the class type or type convertible using autoboxing, a feature that allows transparent conversion between plain data types like int and the corresponding class wrappers like Integer.

The heart of the example lies with the SoftTemperatureWatcher class. We have defined a class whose constructor allows us to specify the temperature that would normally come from the hardware thermometer. It overrides the readThermometerF() method to return the specified temperature. Elevating the visibility of the method and the override are all we have to do in order to make TemperatureWatcher easily testable.

We can extend this approach in several ways.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.251.206