Section 5

A Less Brittle Unit Test Design

I remain unsatisfied with the hints and click action mapping in our application. While playing with the Laser Game for quite a bit I noticed annoying behavior problems with what a cell did when I sometimes clicked on it to push. Sometimes the mirror cell just didn't move the way I expected.

So it's likely we'll be making some fining tuning changes to the constants that are used by the mouse position and region mapping code. Just like we had to do when we make the cells larger, the unit test proved to be very brittle. Before we do anything else to the dimension of click-regions and hint arrow positions, I want to fix the unit test we have so that it's less sensitive to these sometimes necessary changes in our code.

You may remember that there are two test case classes that have this problem: CellClickOutsideRegionRotateTestCase & CellClickInsideRegionPushTestCase.

Let's look at CellClickInsideRegionPushTestCase first. You remember the diagram and table used as the basis for this test method. Here's the existing test case code next to both.

Label Point Push
A 10@10 East
B 30@10 West
C 20@20 North
D 10@30 North
E 30@30 North
F 11@13 East
G 19@21 North
H 29@27 West
I 20@1 South

    | pt regionClass pushRegion testTable cls |
    pt := 11@11.
    regionClass := CellClickRegion clickRegionForPoint: pt.
    self should: [regionClass = CellClickRegionInside].
    testTable := {
    testTable do: [:assoc |
        pt := assoc key.
        cls := assoc value.
        pushRegion := regionClass pushRegionForPoint: pt.
        self should: [pushRegion = cls]]

The values for the data points on the graphic could be expressed as more "relative" locations. For example the points A, B, D and E are meant to be the exact corners of the inside region. And the positions of those corners are derived directly from the CellClickRegionInside #regionRectangle method. The unit test isn't checking that the #regionRectangle is specified correctly. It's trying to figure out if the click points A through I are decoded into push actions correctly.

And if you look carefully at the diagram and the method we have another bug here. The method reflects exactly what's specified in the little table. But look at point "I" on the table. That's not right. The table gives the positon of point "I" as 20@1. But the diagram shows that can't be it. It was obviously a typo but it also makes the test incorrect.

If assume that we can easily obtain and use the Rectangle the represents the CellClickRegionInside we can express the test points more meaningfully. Lets use "rect" to represent that rectangle and make another pass at the table.

Label Point Push
A rect topLeft East
B rect topRight West
C rect center North
D rect bottomLeft North
E rect bottomRight North
F rect topLeft + (1@3) East
G rect center + (-1@1) North
H rect bottomRight + (-1@-3) West
I rect topCenter + (0@1) South

This is much easier to understand. You can read this table and know exactly what this test is trying to accomplish.

Let's change this unit test method to use our new technique. And while we're at it we should fix it .

    | pt regionClass pushRegion testTable cls rect |
    rect := CellClickRegionInside regionRectangle.
    pt := rect topLeft + (1@1).
    regionClass := CellClickRegion clickRegionForPoint: pt.
    self should: [regionClass = CellClickRegionInside].
    testTable := {
        (rect topLeft)                ->    CellClickRegionPushEast.
        (rect topRight)               ->    CellClickRegionPushWest.
        (rect center)                 ->    CellClickRegionPushNorth.
        (rect bottomLeft)             ->    CellClickRegionPushNorth.
        (rect bottomRight)            ->    CellClickRegionPushNorth.
        (rect topLeft + (1@3))        ->    CellClickRegionPushEast.
        (rect center + (-1@1))        ->    CellClickRegionPushNorth.
        (rect bottomRight + (-1@-3))  ->    CellClickRegionPushWest.
        (rect topCenter + (0@1))      ->    CellClickRegionPushSouth
    testTable do: [:assoc |
        pt := assoc key.
        cls := assoc value.
        pushRegion := regionClass pushRegionForPoint: pt.
        self should: [pushRegion = cls]]

That initial calculated point at the beginning of the method was also changed to be specified relative to the region's top left corner. I also changed the formatting a little for improved readability. Re-run our unit test to ensure we haven't broken anything. Everything should pass.

Index Page Next Page

Copyright © 2007, 2008, 2009, 2010 Stephan B Wessels