Chapter 10. Design for Easier Testing

Design for Easier Testing

In this chapter we emerge from the Temple of Doom to find the sun shining brightly and the water a lovely shade of turquoise...[53]

If you've just finished reading Chapter 9, you should now be feeling utterly deflated and at a loss for the sanity of the programming world, to the point where even standing up to make a cup of tea feels like too much effort. So let us lift your spirits with the inverse of the Temple of Doom code base from Chapter 9. In this chapter we'll sweep away the difficult-to-test code and start over with a solid use case, a design, and a clear goal for the finished product. We hope you appreciate the difference, and that within a few pages you'll feel sufficiently uplifted to make that cup of tea. (Whoever said that software development isn't an emotional rollercoaster ride?)

In a moment we'll present the top ten "Design For Testing" to-do list; the bulk of the chapter will be structured around these (just as Chapter 9 was structured around the "don'ts"). However—and this is really the key to easier testing—before we get started on the design, we'll start with a use case. By having a clear idea of what the finished product is meant to do, we'll know exactly what we want to achieve from the design. So if there's any doubt about whether a feature or design element should be included, we can ask: "Is it within the scope of our use case?"

Top Ten "Design for Testing" To-Do List

The following list is really the flipside of the top ten unit testing antipatterns (or "don'ts") from Chapter 9. If you keep the following design criteria in mind while creating a detailed design (see the next chapter), your design will be more amenable to unit testing (and also more pliant, easier to understand and maintain, and more likely to be picked up and used while the code is being cut):

  • 10. Keep initialization code out of the constructor.

    Instead, create a separate initialize() method, to allow the object to be fully constructed before it starts strutting its stuff.

  • 9. Use inheritance sparingly.

    Use inheritance sparingly, and only if it absolutely makes sense to use it. Instead, as a general guideline (but not an all-out rule) use aggregation instead. Use inheritance when you are adding behavior that directly manipulates private data and can do so with minimal dependence on other objects; use aggregation otherwise.

  • 8. Avoid using static initializer blocks.

    A static initialization block is a block of code that gets run the moment a class is loaded into memory—so the code will be run the first time a class is referenced, even in an unassigned variable, by some other class. This is why we refer to these static initialization blocks as "static hair triggers."

  • 7. Use object-level methods and variables instead of static (class-level) methods and variables.

    This rule holds true unless the variable in question is immutable and (if it's a complex object) has immutable data and little or no construction code.

  • 6. Use a Registry or some other pattern instead of the Singleton design pattern.

    While singletons themselves are sometimes necessary, the Singleton design pattern itself has its problems, not least of which is that it makes unit testing difficult. Instead use a Registry class, or whatever mechanism fits your project, to access single instances. Also, question the need for a singleton: does the object in question really need to be the only instance in the system?

  • 5. Keep your classes as decoupled as the application permits.

    Avoid tying your code too tightly to dependent implementations. Instead, allow callers to pass in their own implementation. In other words, "future-proof" your code (and make testing easier) by keeping it free of assumptions about how it'll be used. (That said, sometimes there are legitimate object collaborations driven by the application functionality, e.g., where a class delegates core functions that more properly belong to it.)

  • 4. Keep business logic out of the UI code.

  • 3. Use "Black Box" and "Gray Box" testing so you don't have to break encapsulation.

    Unit tests often need to know about the internals of a class or method in order to perform assertions. While this is sometimes unavoidable, it's a shame to have to break encapsulation in order for a class to be tested. Avoid the issue by writing controller tests first (which are "gray box" in nature), and use unit tests sparingly to fill in the gaps.

  • 2. With variable declarations, reserve the "final" modifier for constants. Generally avoid marking complex types such as service objects as final.

  • 1. Always stick to what's been specified in the use cases and the design.

    Don't be tempted to add complicating features such as extension frameworks "just in case" they'll be needed later... because the chances are that they won't.

There's also a "but above all else" list, which goes like this:

  1. Start by understanding the requirements.

  2. Create a domain model and keep on adding to it whenever you discover new domain objects.

  3. Analyze the requirements by breaking them down into use cases.

  4. Do a conceptual design (robustness analysis).

  5. Know exactly which controller tests you're going to write—along with the inputs and acceptance criteria—before you do your detailed design (and definitely before you begin coding).

  6. Spend time on the detailed design, preferably with more than one person involved, and definitely involving the people who will be writing the code.

The Temple of Doom—Thoroughly Expurgated

To render the Temple of Doom "design" (we hesitate to call it that) rigorously scrubbed-up, wiped down, demolished and rebuilt with reuse and testability in mind, we need to go back to basics... start with a use case, analyze it, create a design, and so forth. So, in "capsule summary" form, that's what we'll do in this chapter. Our effort actually forms a useful exercise in retrospectively looking at some legacy code and rethinking it so that unit tests can be added.

The Use Case—Figuring Out What We Want to Do

First, then, the use case. The core of the Temple of Doom code was unmistakably the HotelPriceCalculator class. So the use case should have to do with displaying the price for a hotel. This use case kicks into action immediately after the user has searched for a hotel. Here's a description of the use case in text form:

Use Case: Quote Hotel Price

BASIC COURSE:

The system displays the Hotel Details page. The user types in the number of nights he would like to stay for, and clicks the Calculate button. The system calculates the price based on the cost per night, and displays the result.

ALTERNATE COURSES:

(As this is just a quick capsule summary, we won't delve into the alternate courses. See Chapter 6 for a more complete example.)

Figure 10-1 shows the robustness diagram to accompany this use case.

Rethinking the Temple of Doom—robustness diagram, Part 1

Figure 10-1. Rethinking the Temple of Doom—robustness diagram, Part 1

Here's where we immediately start to see a benefit of going back to the use case, and doing some simple analysis work. Looking at the diagram, the Retrieve latest price controller looks a bit lonely. That loneliness begs the question, "Where's the controller actually getting the latest price from?" In the legacy code[54] from the start of this chapter, there's the puzzling presence of a hotel price streamer. As we commented at the time, this would be more in place within a financial app. We kept it around "just in case" it really is needed. But now that we have a use case to refer to, it's obvious that there's no place for streaming price updates here. We simply need a way of querying and fetching a live price for the hotel in question.

Figure 10-2 shows the revised use case description and robustness diagram. Already, you just know the end result will be simpler and more focused, as we won't have to worry about unnecessary streaming price updates, and all the associated plumbing code.

Rethinking the Temple of Doom—robustness diagram, now magic-free

Figure 10-2. Rethinking the Temple of Doom—robustness diagram, now magic-free

Identify the Controller Tests

We're now well positioned to identify the controller tests, based on the robustness diagram in Figure 10-2. As we stated earlier, ours is just a quick example, so we're not delving into alternate courses (which is really where you see some major benefits from controller testing). So with that in mind, let's next use EA to generate the test cases for our robustness diagram (see Figure 10-3). Remember that you want one test case per controller. Each test case starts out with a "Default Run Scenario," but you can add more scenarios as you think of them.

Controller test cases for the Quote Hotel Price use case/robustness diagram

Figure 10-3. Controller test cases for the Quote Hotel Price use case/robustness diagram

You would expect to create tests for all of the controllers shown in Figure 10-3. However, for this chapter's example we'll focus on the Calculate Overall Price test case.

Calculate Overall Price Test

We construct a test to calculate the overall price. This test consists of just the one scenario, as shown in Table 10-1.

Table 10-1. Test Scenarios for the Calculate Overall Price Test Case

Test Scenario

Description

Input

Acceptance Criteria

Default run scenario

The system calculates the total price.

Price per night, Number of nights

The correctly calculated Overall Price should be returned.

We can now transform the test scenarios into a test class using the ICONIX/EA JUnit 4 transform, and generate the Java test class. We show this class in Figure 10-4.

Our Java test class

Figure 10-4. Our Java test class

The following is the code generated by EA, ready for us to fill in the gaps:

public class CalculateOverallPriceTest {

    @Before
    public void setUp() throws Exception {
    }

    @After
    public void tearDown() throws Exception {
    }

    /**
     * The system calculates the total price.
     * @input Price per night, Number of nights
     * @AcceptanceCriteria The correctly calculated Overall Price should be returned.
     */
    @Test
    public void testDefaultRunScenario() throws Exception {
    }
}

This code gives us one test method (for this brief example). The method comments remind us that the test should provide two inputs (Price per night, and Number of nights), and then check that the returned Overall Price is correct.

Retrieve Latest Price Test

To recap (see the robustness diagram in Figure 10-2), the Retrieve Latest Price controller fetches the latest price for the current Hotel, then hands over to the Calculate Overall Price controller. Table 10-2 shows the one test scenario for Retrieve Latest Price.

Table 10-2. Test Scenarios for the Retrieve Latest Price Test Case

Test Scenario

Description

Input

Acceptance Criteria

Default run scenario

The system queries the Hotel Price Service for the current price per night.

The selected Hotel ID

The current price per night is returned.

As with CalculateOverallPriceTest, we can now transform this into a test class using the ICONIX/EA JUnit 4 transform, and generate the Java test class shown in Figure 10-5.

Test class to retrieve the latest price

Figure 10-5. Test class to retrieve the latest price

The following is the test skeleton generated from the UML class shown in Figure 10-5:

public class RetrieveLatestPriceTest {

    @Before
    public void setUp() throws Exception {
    }

    @After
    public void tearDown() throws Exception {
    }

    /**
     * The system queries the Hotel Price Service for the current price per night
     * @input The selected Hotel ID
     * @AcceptanceCriteria The current price per night is returned.
*/
    @Test
    public void testDefaultRunScenario() throws Exception {
    }
}

Design for Easier Testing

We've reached the detailed design stage, and the core of this chapter. So we'll now walk through the top ten "Design for Testing" to-do list. Each item in this list is really the flipside of the "don't" list in Chapter 9; therefore, each item is also a solution to a particular unit testing antipattern. So, Don't #10, "The Complex Constructor," is solved with to-do item #10, "Keep initialization code out of the constructor," and so on.

What you will find is that many of the problems solved here tend not to arise if you follow the ICONIX Process and DDT, e.g., allocating behavior to entity classes during sequence diagramming—essentially a domain-driven/responsibility-driven design approach. However, these coding problems are still well worth keeping in mind[55] (it's a case of "know your enemy").

10. Keep Initialization Code Out of the Constructor

Cast your mind back to the Complex Constructor antipattern described in Chapter 9. This antipattern led to all manner of problems, making HotelPriceCalculator difficult to test. So, what went wrong? Quite simply, the programmer confused object construction with object initialization. Constructing an object should be a simple case of setting any dependent objects that were passed in as arguments to the constructor. Initializing the object—doing further work to prepare it for use—is a different matter, and often involves complex code. This kind of code belongs in separate methods; lumping it all into the constructor isn't particularly OO.

In the case of HotelPriceCalculator, the perfect constructor, cutting out all of the initialization stuff, would be this:

public HotelPriceCalculator(Object ... hotelIDs) {
    this.hotelIDs = hotelIDs;
}

The only fly in the ointment here is that there's an implicit call to the superclass's no-arg constructor, so this constructor still may not be as straightforward as it looks—but more about that in the next section.

Of course, all of that object initialization that we've cut out still needs to take place. It might not have to take place for the particular test being run, but the product code will certainly need it to happen. You should end up seeing this sort of pattern:

HotelPriceCalculator priceCalc = new HotelPriceCalculator(hotelIDs);
priceCalc.initialize();

Within initialize(), the very last thing it should do is hand out references to "this" (e.g., handing a reference to PriceStreamer). That way, you know the object's fully initialized before it starts advertising its existence to other objects. So the code, at this stage, should look something like this:

public void initialize() {
    // many lines of code to load up a list of items from the DB
    // many more lines of code to initialize some part of
    // HotelPriceCalculator that may or may not be needed
    PriceStreamer.getInstance().addListener(this);
}

From a unit testing perspective, the real benefit of separating construction from initialization is that it provides a breathing space, a chance to set mock services (e.g., a mock PriceStreamer or stubbed-out DB object), or even to choose not to call initialize() at all if it's not needed.

What if your test needs, say, the PriceStreamer listener to be added, but none of that other stuff? Easy, just split up the initialize() method:

public void initialize() {
    loadHotels();
    initOtherParts();
    theDreadedEtc();
    addExternalListeners();
}

Again, this is just good design: each method does one thing. But it also makes unit testing a whole lot easier, as your test's setUp() method can choose to avoid calling initialize() altogether, and instead simply call the init methods that it needs to—in this case, addExternalListeners().

9. Use Inheritance Sparingly

This to-do item solves antipattern #9, the Stratospheric Class Hierarchy. Whenever you encounter a teetering stack of inheriting classes, you'll almost always find that the reason for the inheritance is dubious at best. In the case of HotelPriceCalculator, it's highly questionable whether it needs to be a collection of Hotels at all. As you'll see later in this chapter, the reason for the class's existence—the calculatePrice() method—operates only on one Hotel at a time. In fact, it's only using the parent HotelCollection as a local cache of Hotel objects read in from the database. It's symptomatic of the kind of "design" that may have evolved without a clear picture of the overall system, or without any clear design thought whatsoever.

Note

We should emphasize the ludicrousness of a HotelPriceCalculator being a type of HotelCollection. Of course, it doesn't make sense... and it's precisely the kind of error that's caught if you begin your project with some up-front domain modeling. In a collaborative activity, we're sure somebody in the group would have called out: "Hang on, how can a price calculator 'be' a collection of hotels?" Thus the error would have been caught early on, and a load of strife during design, coding, and testing would have been prevented. By now you've hopefully seen numerous discussions of domain modeling elsewhere in the book.

So a "quick fix" would be to make HotelPriceCalculator totally divorced from HotelCollection. But, as you might expect, the real answer is to return to the original use case—and if there wasn't one, write one now!—then do some conceptual design work based on the use case, write some controller tests, design the solution, fill in any remaining gaps with unit tests, and write the code. It's no surprise that the result will almost certainly be free of the majority of problems that we describe in this chapter.

We won't walk through this process right now (we're assuming you've digested Part 2 already), but let's say we arrived at the conclusion that HotelPriceCalculator is not a HotelCollection, but instead could simply have a HotelCollection (yes, it's that old "inheritance vs. aggregation" chestnut). Figure 10-6 shows an example of such a collection.

HotelPriceCalculator has a HotelCollection, but... why can't a Hotel calculate its own price?

Figure 10-6. HotelPriceCalculator has a HotelCollection, but... why can't a Hotel calculate its own price?

There are some important changes in this class diagram: the most obvious one is, of course, that HotelPriceCalculator is no longer a type of HotelCollection, but instead contains one via its hotels attribute. Another change is that the array of hotel IDs is no longer passed into HotelPriceCalculator's constructor: instead, there's a loadHotels(hotelIDs) operation. The calling code can call this setup method if it needs HotelPriceCalculator to load some Hotels and populate its HotelCollection. But if (as is the case with our unit tests) it doesn't need that to happen, then the operation can be skipped altogether.

In fact, as you'll see later in this chapter when we get on to the detailed design for the "scrubbed-up" version, HotelPriceCalculator itself ends up not being needed at all. Instead, we put the behavior where the data lives—so the price calculating method ends up on Hotel.

8. Avoid Using Static Initializer Blocks

In Chapter 9 we talked about the perils of the static initializer block (or "static hair trigger"). Here is by far the best solution to this particular antipattern:

Just... don't do it!!

We could elaborate, but it really is as simple as that. Static hair triggers are evil incarnate, designed purely to give innocent programmers everywhere a hard time. Any code that's in the static block should just be moved to the initialize() method that we outlined earlier in this chapter. This simple action moves the static initialization to the object level, meaning it'll be run each time an object of that type is initialized. Most of the time, this is fine... e.g., our example has a static Logger. But does a logger really need to operate only at the class level?

Separating out the logger results in code that looks like this:

public class HotelPriceCalculator {
Logger logger;

    public void initialize() {
        initLogger();
    }

    private void initLogger() {
        logger = Logger.getLogger("not.the.real.mapplet");
    }
}

There are, admittedly, rare cases when initialization code must be run only once in a program's lifetime. But in such cases, the static initializer block still isn't the answer, as we return to the question of error handling. In such cases, you'll need a more robust design that blocks during initialization, and throws an error if initialization failed.

7. Use Object-Level Methods and Variables

Remember the "too many chefs spoil the broth" problem from Chapter 9? The solution to the chef problem is, assuming that 11 of the chefs can't be fired, to give each one his or her own saucepan. Don't make use of static class-level methods and variables. Assign members to objects, not to classes.

Let's return to HotelPriceCalculator and its static members, nyInstance and nyHotels. These are both symptomatic of a dysfunctional design. Why on earth would a price calculator class need to contain a reference to a bunch of New York hotels? Some further digging suggests that this code was added in by a well-meaning programmer who wanted to test the price calculator with specific data, but hadn't heard of unit tests. So we have the remnants of test code mixed in with the product code, obfuscating the design. The code serves no other purpose, so—in this case at least—the answer's easy: delete it!

6. Avoid the Singleton Design Pattern

In Chapter 9 we donned our pantomime outfits and jumped around the stage encouraging you to boo and hiss at the Singleton design pattern; we also demonstrated why this particular pattern is so problematic. But for all of that, there is frequently a need for a class that has only one instance.

There are two solutions to the problem of creating a single-instance object. Which to use depends upon whether you genuinely need a single-instance-only object. Here are the two solutions:

  • Don't make your object a Singleton (capital S): Allow any class to create and dispose of instances of your one-object class.

  • Apply some other framework or pattern: Deem your class to be a singleton (lowercase S), but use some other framework or pattern to achieve that goal. For example, apply the Spring/Inversion of Control pattern, which maintains the object's lifecycle for you. Or apply a Registry class that your run-time code can use to get at a single instance of a class without preventing unit tests from instantiating their own limited-scope copy.[56]

Quite often, an object that must be a singleton in the deployed product doesn't have the same requirement when it's being unit-tested. In this case, Option 2 is the better option. In all other cases, give yourself a break and go for Option 1.

For PriceStreamer, we've gone with Option 2. This does mean that an extra Registry class is involved, but the improvements in stability, extensibility, testability, all those "ilities," make it worthwhile—especially as it can also be used to store any other "deemed-singleton" objects that you need. The registry class isn't used by the unit tests; they can simply create their own instance via the PriceStreamer's public constructor.

You don't need a big framework to do what's needed, though. Object registries are easy to implement. Here's a simple one in its entirety:

public class SimpleRegistry {

    public static final Map<Class, Object> lookup =
                  new HashMap<Class, Object>();

}

To further simplify things, any object that uses the registry can do a static import at the top of the class. Here is an example:

import static com.softwarereality.nottherealmapplet.SimpleRegistry.*;

In the program's startup code, add this line to create your single instance:

lookup.put(PriceStreamer.class, new PriceStreamer());

Tip

You can further decouple this design by defining an interface for PriceStreamer, and using the interface as the lookup key instead of the concrete class.

And then getting your single instance is as simple as doing this:

PriceStreamer streamer = (PriceStreamer) lookup.get(PriceStreamer.class);

This approach limits the use of static members to a single place in the code. Of course, it still violates the principle of encapsulation that we've discussed in this chapter. However, the reason it gets away with it is that the sole purpose of the registry class's existence is to create and keep a single instance of PriceStreamer and other objects. If there were any further code in this class, or (shudder) actual business logic, then we would be thumbing through our Yellow Pages for a local Rent-a-Deity to smite the Registry class down from on high, leaving nothing but a couple of smoking boots where it once stood. (Or call on Ixtab the suicide goddess if she's available for parties).

The benefit of the keep-it-simple approach in this section can be seen straightaway in the more robust unit test setup. Remember that the tests don't need to use the Registry, as they want a fresh copy of PriceStreamer so that each test remains deterministic:

@Before
public void setUp() throws Exception {
    streamer = new PriceStreamer();
}

The two test methods—quotePrice_PriceAdded() and quotePrice_NoPriceYet()—remain unchanged, but the order in which they're run no longer matters. Result: more robust tests, which shouldn't randomly fail just because they're executed in a different order.

5. Keep Your Classes Decoupled

In Chapter 9 we illustrated the problem of code that is too tightly bound to its dependencies. In the example, HotelPriceCalculator was difficult to test because it assumed a particular implementation of PriceStreamer. The solution—if we definitely want the test to just be about HotelPriceCalculator, and not to be affected by the implementation of PriceStreamer—is to pass in a stubbed-out version, which will return a price for one night at hotel "123," which the method under test can then use in its calculation. Using the combined magic of Mockito and Java reflection, this is as simple as the following:

PriceStreamer streamer = mock(PriceStreamer.class);
when(streamer.quotePrice("123")).thenReturn(new BigDecimal(225.0));

The first line creates a mock instance of PriceStreamer. The second line configures the mock so that when its quotePrice() method is called with "123," it'll return 225.0.

We also need a way to tell HotelPriceCalculator to use this version, without going overboard on Inversion of Control frameworks or otherwise increasing the code's complexity. Luckily, we've already separated out the initialization code, so it's a simple case of the test not calling calc.initPriceStreamer(), and instead just assigning the mock streamer into the calc.streamer field.

Here's the complete test:

public class HotelPriceCalculatorTest {

    HotelPriceCalculator calc;
    PriceStreamer streamer;

    @Before
    public void setUp() throws Exception {
        streamer = mock(PriceStreamer.class);
        calc = new HotelPriceCalculator();
        calc.streamer = streamer;
    }

    @Test
    public void calculatePrice() throws Exception {
        Hotel hotel = new Hotel("123");
when(streamer.quotePrice("123")).thenReturn(new BigDecimal(225.0));

        // Calculate the total price:
        BigDecimal price = calc.calculatePrice(hotel, 3);

        assertEquals(new BigDecimal(675.0), price);
    }
}

HotelPriceCalculator is also looking nice and straightforward now, far removed from the bamboozling mess that you saw in Chapter 9. Here's the relevant code for the calculatePrice() test (the complete class isn't much bigger than what you see here):

public class HotelPriceCalculator {

    PriceStreamer streamer;

    public void initialize() {
        initPriceStreamer();
    }

    private void initPriceStreamer() {
        this.streamer = new PriceStreamer();
        SimpleRegistry.lookup.put(PriceStreamer.class, streamer);
    }

    public BigDecimal calculatePrice(Hotel hotel, int numNights) {
        Object id = hotel.getId();
        BigDecimal pricePerNight = streamer.quotePrice(id);
        return pricePerNight.multiply(new BigDecimal(numNights));
    }
}

4. Keep Business Logic Out of the UI Code

If you recall from Chapter 9, we had a Flex/MXML-based Hotel Search screen. Now that the use case is better understood, it's obvious that this screen was just completely wrong. To match the use case, the user has already found the hotel, and wants to see prices for it. To recap, here's the basic course:

The system displays the Hotel Details page. The user types in the number of nights he would like to stay for, and clicks the Calculate button. The system calculates the price based on the cost per night, and displays the result.

Figure 10-7 shows a screenshot of the component implementing the use case. The user sees a simple web page showing a photo of a nice hotel room, the price per night, and the total price for the user's planned stay.

A Flex UI matching the Quote Hotel Price use case. Compare with the "guessed-at" UI from Chapter 9.

Figure 10-7. A Flex UI matching the Quote Hotel Price use case. Compare with the "guessed-at" UI from Chapter 9.

Another problem with the Flex code from Chapter 9 was that the form validation logic was closely tied into the UI markup. There was little or no OO encapsulation going on. To fix this, we could create a PriceSearch class and give it properties matching the form. Here's the beginning of such a class, before any behavior is added:

public class PriceSearch
{
    private var numNights: int;
    private var hotelID: String;
}

But if you check the screenshot in Figure 10-7, it's obvious that this would have been created from a Hotel object... so why not simply add a quotePrice() method to Hotel? Here's how it could look:

public class Hotel
{
    private var id: String;
    public var pricePerNight: Number;

    // . . .
public function quotePrice(numNights: int): Number
    {
        if (isNaN(Number(nights)))
        {
            throw new ValidationError("Please enter a valid number of nights.");
        }

        var numNights: int = parseInt(nights);
        if (numNights < 0)
        {
            throw new ValidationError("Please enter a positive integer.");
        }

        // Call the remote Java service:
        return PriceService.fetchQuote(this, numNights);
    }
}

Notice how the validation is now being done in this non-UI class, not in the UI mark-up. The quotePrice() function throws a ValidationError if the input fails validation. We ought to catch this in the UI button-click event handler, and show an appropriate dialog box.

Here are the relevant components from the UI mark-up:

<s:TextInput id="txtNights" />
<s:Button click="btnCalculate_clickHandler(event)" id="btnCalculate" label="Calculate Price"/>
<s:Label text="{totalCostOfStay}" />
<s:Label text="${hotel.pricePerNight}" />

And here's the ActionScript to go with it—notice how both hotel and totalCostOfStay are bindable:

import mx.controls.Alert;

[Bindable]
private var hotel: Hotel = new Hotel();

[Bindable]
private var totalCostOfStay: String;

protected function btnCalculate_clickHandler(event:MouseEvent):void
{
    try
    {
        totalCostOfStay = "$" + hotel.quotePrice(txtNights.text);
    }
    catch (error: ValidationError)
    {
        Alert.show(error.message, "Validation Error");
    }
}

Note

If you look at the ActionScript code, the totalCostOfStay variable is tagged as "bindable." So when its value is changed, the Total Cost label will be automatically redrawn with the new value. The Hotel object is also bindable, because we're using its pricePerNight value in the "price per night" label.

In the button click handler, the hotel's quotePrice() function is called with the text value of txtNights. If any validation fails (non-numeric or negative value entered), the UI code catches the ValidationError and displays an alert dialog—see Figure 10-8.

Running a unit test is one thing, but good old visual inspection—seeing exactly what the user sees—is important, too.

Figure 10-8. Running a unit test is one thing, but good old visual inspection—seeing exactly what the user sees—is important, too.

Adding a unit test for the validation code should now be pretty straightforward, as Hotel is a non-UI class. We can further split out the validation from the quotePrice() function to make it easier still. The two functions in Hotel are now the following:

public function quotePrice(nights: String): Number
{
    validateNumNights(nights);
    var numNights: int = parseInt(nights);
return PriceService.fetchQuote(this, numNights);
}

public function validateNumNights(nights: String)
{
    if (isNaN(Number(nights)))
    {
        throw new ValidationError("Please enter a valid number of nights.");
    }

    var numNights: int = parseInt(nights);
    if (numNights < 0)
    {
        throw new ValidationError("Please enter a positive integer.");
    }
}

Here's the FlexUnit 4 unit test code for Hotel.validateNumNights():

public class HotelTest
{
    private var hotel : Hotel;

    [Before]
    public function setUp(): void
    {
        hotel = new Hotel();
    }

    [After]
    public function tearDown(): void
    {
        hotel = null;
    }

    [Test]
    public function testValidateNumNights():void
    {
        // Expect no ValidationError to be thrown:
        hotel.validateNumNights("1");
    }

    [Test(expects="ValidationError")]
    public function testValidateNumNights_Negative():void
    {
        hotel.validateNumNights("−1");
    }

    [Test(expects="ValidationError")]
    public function testValidateNumNights_NonNumeric():void
    {
        hotel.validateNumNights("xyz");
    }
}

The three test functions are pretty straightforward. The first test is the "basic course": a valid number is entered, so we don't expect a ValidationError to be thrown. The other two functions test for a negative number and a non-numeric number respectively: for both of these, we expect a ValidationError, and the test fails if we don't get one. Figure 10-9 shows the warm and snuggly green bar when we run the test class in Flash Builder.

Green bar of Shangri—oh, you get the idea...

Figure 10-9. Green bar of Shangri—oh, you get the idea...

The main point here is to compare this code with the corresponding "antipattern #4" in Chapter 9: that version would have been impossible to unit-test, whereas with the validation code separated into a non-UI class, it's now very straightforward.

3. Use Black Box and Gray Box Testing

The reason to use black/gray box testing is to avoid breaking encapsulation. In Chapter 9, in the "Privates on Parade" antipattern, we illustrated the problem where a unit test needs to get inside an object to test its state, but it can't because the field in question is private. There are a couple of easy solutions to this problem:

  • Make the field package-private instead of private

  • Add a getter

These solutions both involve increasing the visibility of the field, either by making the field directly accessible to the unit test,[57] or by exposing the field in a "getter" method. Creating a public getXYZ method is the less desirable of the two, as it pollutes the class's interface, making a field public that is relevant only to the internal workings of the class. Conversely, simply making the field package-private (where only classes in the same package can access the field) does less damage to the class design. But it's still not ideal, and doesn't quite "ring true" as good design practice, as you're still exposing a field for the purpose of the unit test.

Luckily, there are also some more profound solutions—and this is another place where DDT and TDD differ in their approach:

  • Don't test that specific field—checking the value of a "wannabe private" field makes the test know too much about the internals of the class under test.

  • Test the result at a broader level—in other words, involve a controller test that relies on hotelDetailsFound being set in order to achieve a more business-level goal.

Our preferred solution out of all of these is the final one—testing the result at a broader level. If you find that you're considering increasing the scope of a field to test its value, then you should question the value of the test itself. Tests that "grub around" in the internals of a class are more brittle than tests that simply check the result of an operation using a class's published (or public) interface.

Note

Chapter 6 illustrates in detail how to create controller tests that virtually eliminate the problem of unit tests needing to know too much about the code under test.

Thus armed with an essential set of design guidelines to make software easier to test, let's now return to our "rebooted" version of the hotel price calculator.

2. Reserve the "Final" Modifier for Constants—Generally Avoid Marking Complex Types Such as Service Objects as Final

This suggestion is purely from a testability perspective, and may not always be good advice when it comes to systems design, especially in a multi-threaded environment. It's a design decision, of course: if you need to ensure that a reference is immutable, then mark the reference as final. However, this might make the code harder to test, as it means that the unit test code won't be able to substitute its own mock implementation.

One way around this is to assign the complex type on construction:

public class DBManager {

    private final DBConnectionManager dbManager;

    public DBManager(DBConnectionManager dbManager) {
        this.dbManager = dbManager;
    }
}

This will allow a test to still pass in its own mock implementation—assuming that it's instantiating this particular object directly.

1. Stick to the Use Cases and the Design

Of course, requirements do change and so does the design from time to time. But if it does, the worst approach is the "refactoring free-for-all," in which the code is swiftly pushed further and further away from any documented agreed-upon specification of what the customer's asking for, or from the design that the team collectively agreed on during design workshops. The time spent figuring out whether a unit test should be rewritten or abandoned after it breaks (because nothing ties back to the requirements or the design anymore) could have been saved by just a little time updating the use cases and the design model.

We illustrate some best practices for keeping the code and the design/use cases in-sync in our upcoming book, ArcGIS Server Best Practices (ESRI Press, 2010).

Detailed Design for the Quote Hotel Price Use Case

As you can see in Figure 10-10, the detailed design for the Quote Hotel Price use case is pretty straightforward. The design does exactly what it says on the tin (or in the use case text, in this case).[58]

Detailed design for the Quote Hotel Price use case

Figure 10-10. Detailed design for the Quote Hotel Price use case

You should already be seeing some big differences between this and the original design. The main change is that there isn't even a HotelPriceCalculator class anymore; the calculating behavior exists as a method on Hotel itself. This follows a key rule of OO design: put the behavior where the data lives.

We can now create an outline of our new Hotel class, following along with the design in Figure 10-10. Here's the result (the only bit that isn't shown in the diagram is retrieving the Hotel by its ID):

public class Hotel {

    private String id;

    public Hotel(String id) {
        this.id = id;
    }

    public BigDecimal quotePrice(int numNights) throws Exception {
        HotelPriceService service = (HotelPriceService) lookup.get(HotelPriceService.class);
        BigDecimal pricePerNight = service.fetchPrice(id);
        return calculateOverallPrice(pricePerNight, numNights);
    }

    BigDecimal calculateOverallPrice(BigDecimal pricePerNight, int numNights) {
        return null;  // TODO (see next section)
    }
}

Controller Test: Calculate Overall Price

The quotePrice() method can be strung together just from the messages shown in the diagram. This leaves us with one method to complete: calculateOverallPrice(), which will do the actual calculation. To test calculateOverallPrice(), let's return to the CalculateOverallPriceTest class that EA generated for us, and complete the controller test method:

/**
 * The system calculates the total price.
 * @input Price per night, Number of nights
 * @AcceptanceCriteria The correctly calculated Overall Price should be returned.
 */
@Test
public void testDefaultRunScenario() {
    Hotel hotel = new Hotel("123");
    BigDecimal pricePerNight = new BigDecimal(220.0);
    int numNights = 5;
    BigDecimal quote = hotel.calculateOverallPrice(pricePerNight, numNights);
    assertEquals("The calculated price per night should be 220*5", new BigDecimal(1100),
quote);
}

This test code creates a Hotel object, then calls its calculateOverallPrice() method with a "predestined" pricePerNight value and number of nights. It then asserts that the value returned is the one expected. Running this straightaway gives us the Red Bar of Angry Marauding Goblins. So we can now fill in the product code to make the test pass:

BigDecimal calculateOverallPrice(BigDecimal pricePerNight, int numNights) {
    return pricePerNight.multiply(new BigDecimal(numNights));
}

Now, rerunning the test produces the Green Bar of Hazy Summer Days Spent Fishing by the River... altogether more preferable, we think.

As we're on a roll, let's also take a look at the other important test case here, Retrieve Latest Price Test.

Controller Test: Retrieve Latest Price Test

We're now faced with the problem that we want the code under test to call out to the remote Price Service, but we also want to keep the controller tests self-contained (see Chapter 11 for a discussion of "controller-level integration tests," which are possible and perfectly valid, but can be difficult to maintain). So... we could create a mock HotelPriceService here, and assert that when we tell Hotel to call it, then the mock object gets called. It would look something like this:

/**
 * The system queries the Hotel Price Service for the current price per night
 * @input The selected Hotel ID
 * @AcceptanceCriteria The current price per night is returned.
 */
@Test
public void testDefaultRunScenario() throws Exception {
    HotelPriceService service = mock(HotelPriceService.class);
    SimpleRegistry.lookup.put(HotelPriceService.class, service);
    when(service.fetchPrice("123")).thenReturn(new BigDecimal(225.0));
verify(service).fetchPrice("123"); // asserts that the method under test was
definitely called
        Hotel hotel = new Hotel("123");
        hotel.quotePrice(5);
    }

But what do we actually gain from this test? (Hint: not an awful lot.) If there was some code that genuinely needed a mock object, in order to allow it to be tested, then we would add one. But this is one of those situations where the mock is simply satisfying the test[59], and we get the satisfaction of increasing the overall test count, which sounds good when related in the early morning stand-up meeting... but provides absolutely no business (or technical) value whatsoever. So we won't do that, then!

The Rebooted Design and Code

To finish up, here's the fully rebooted design and code. Figure 10-11 shows the (much simplified) class diagram, now completely tangle-free. Compare this with the original Temple of Doom class diagram in Figure 9-1 to get the full effect.

Class diagram for the Quote Hotel Price use case

Figure 10-11. Class diagram for the Quote Hotel Price use case

Here's the complete Hotel class:

package com.softwarereality.nottherealmapplet;

import static com.softwarereality.nottherealmapplet.SimpleRegistry.lookup;
import java.math.BigDecimal;

public class Hotel {
private String id;

    public Hotel(String id) {
        this.id = id;
    }

    public BigDecimal quotePrice(int numNights) throws Exception {
        HotelPriceService service = (HotelPriceService) lookup.get(HotelPriceService.class);
        BigDecimal pricePerNight = service.fetchPrice(id);
        return calculateOverallPrice(pricePerNight, numNights);
    }

    BigDecimal calculateOverallPrice(BigDecimal pricePerNight, int numNights) {
        return pricePerNight.multiply(new BigDecimal(numNights));
    }
}

HotelPriceService, in "real life," would implement a REST client (or similar HTTP/web service type of thing) to make calls to a remote service. For the example, we've used a simple, stubbed-out version:

public class HotelPriceService {
    public BigDecimal fetchPrice(String hotelID) {
        return new BigDecimal(300.0);
    }
}

Finally, SimpleRegistry—which allows us to maintain a single instance of HotelPriceService without adopting the problematic Singleton design pattern—is as simple as ever:

public class SimpleRegistry {
    public static final Map<Class, Object> lookup = new HashMap<Class, Object>();
}

We hope that gives you a good taste for the way in which going back to the use cases, talking to customers about what's really needed, doing some up-front design, and creating controller tests early on, all come together to make the code much easier to test... and also radically simplify the design. The example that we started the chapter with might seem like an extreme case, but we've seen production code like this way too often, where developers give up on unit testing because it's "Too Damn Difficult." Yet if they followed the process we've just described, the result would profoundly improve their code. Great swathes of code, boilerplate/plumbing classes, and so on, which might seem to be essential, suddenly turn out not to be.

Summary

This chapter presented the flipside to the antipatterns from Chapter 9. As you saw with the "expurgated" price calculator design, the introduction of a use case and controller testing resulted in a far simpler design, where the majority of design issues never even arose. There was no need to refactor them out of the code, as the antipatterns never even made it into the design.

In the next chapter we look at an advanced topic, but one that we wholeheartedly recommend that you introduce into your project once you're familiar with DDT (which hopefully you are by now!): automated integration testing.



[53] You may have to take our word for the turquoise part... or, alternatively, check out the original image, along with more of Doug's photography from Tulum and Cancun (including 360-degree virtual tours), at www.vresorts.com/VRPages/Cancun/Cancun.html.

[54] When applying this technique to your own project, you can safely start to call the original code "legacy code," because it's being revisited with an eye towards refactoring it (even totally replacing parts of it). This change in stance has a remarkable psychological effect, producing a much-needed objectivity towards the code. Whereas you may have been wary of changing or ripping out old, established parts of the code base, suddenly you'll find that you're not wary of changing the code anymore. By creating use cases and talking to the business users, you know what the code is really meant to be doing, so there's less danger of accidentally removing needed functionality. The controller tests, when you add them, will make it safer still to go at the legacy code with a finely honed machete.

[55] It's shocking, we know, but we have actually heard of instances of programmers "going rogue" and deviating from the specified design.

[56] A registry of objects is a single go-to place, a one-stop shop for all your single-instance needs. A "registry" is the basis for Cairngorm, Adobe's "official" application framework for Flex. Sometimes denigrated as being the reintroduction of global variables, it does at least solve the occasional case where you genuinely need a singleton object. Spring Framework takes a different approach, by allowing you to mark a Java bean as a singleton; the framework takes care of the lifecycle details without preventing unit tests from creating their own copies.

[57] In Java, package-private (or "default access") fields can be accessed by other classes as long as they're in the same package. Conventionally, unit tests are placed in a different root folder to the product code; but they can still be in the same package/namespace, giving them access to the package-private fields.

[58] Having said that, we've omitted the first part of the use case—displaying the Hotel Details page—from the sequence diagram, as it isn't really within the scope of this refactoring example.

[59] Or, as our good friend Alice might say: "Painting the unit test green."

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

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