Cartesian Plane Lesson 6: Testing (Page 2)

JUnit, Assertions, Graphics

In this, the conclusion of our lesson focusing on JUnit, we will develop a test driver for the LineGenerator class. Along the way, we’ll also learn a couple of new features offered by JUnit.

GitHub repository: Cartesian Plane Part 6

Previous lesson: Cartesian Plane Lesson 6: Unit Testing (Page 1)

Unit Testing LineGenerator

The LineGenerator class can distinguish between axial- and nonaxial- lines but nothing else; for example, it doesn’t know what a tic mark or a gridline is. And it doesn’t know anything about weight or color. It generates Line2D objects of a given length at a position relative to the axes and the edges of the enclosing rectangle. So a major focus of our testing is on position and length.

Since we have many constructors*, we must ensure that all constructors implement the correct default values. For example, when we invoke LineGenerator(Rectangle2D rect, float gridUnit, float lpu) we have to verify that the length defaults to -1 and the orientation defaults to BOTH.

*Two thoughts on constructors in the context of testing:

  1. If multiple constructors are complicating the testing process, we might consider eliminating some of the constructors. We have to balance convenience to the developer with the cost of development and maintenance. Consider:
    • How often are developers going use a given constructor?
    • How much convenience do developers derive from all those constructors? On this topic let’s acknowledge that:
      • All those constructors might reduce development time significantly.
      • If instantiating a particular class entails a complex calculation for a parameter, multiple constructors can be helpful in confining the calculation to a single place (the constructor) rather than requiring the developer to perform the calculation. This would reduce the complexity of the code, leading to a product that is easier to maintain and enhance.
    • How difficult will it be to write a unit test for a particular constructer?
    • What impact does unit test development have on the overall development schedule?

The LineGeneratorTest Class

Reviewing this lesson a considerable time after it was first written, I find myself tempted to change a lot of the original code. And eventually perhaps I will. You should keep an eye out for improvements that you might wish to make; in particular, testing the constructors can be simplified.

Below, find the details of the LineGenerator JUnit test class, LineGeneratorTest.

Infrastructure

In the following paragraphs, we will discuss the fields and private methods of LineGeneratorTest, in addition to its @BeforeEach method.

⬛ Fields
Here is an annotated list of the LineGeneratorTest class’s fields. See also the @BeforeEach method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
private static final int    baseRectXco     = 100;
private static final int    baseRectYco     = 2 * baseRectXco;
private static final float  baseWidth       = 200;
private static final float  baseHeight      = baseWidth + 100;
private static final float  baseGPU         = 50;
private static final float  baseLPU         = 1;
private static final float  baseLen         = 15;

private Rectangle2D testRect;
private float       testWidth;
private float       testHeight;
private float       testGPU;
private float       testLPU;
private float       testLen;
  • Lines 1-7: Default test parameters:
    • Lines 1,2: Default coordinates of the rectangle where the lines are to be drawn.
    • Lines 3,4: Default dimensions of the rectangle where the lines are to be drawn.
    • Line 5: Default GPU for drawing lines.
    • Line 6: Default LPU for drawing lines.
    • Line 7: Default line length.
  • Lines 9-14: Test parameters. These fields are set to their default values at the start of every test. They may be modified during test execution. See also the @BeforeEach method.
    • Line 9: The rectangle where the lines are to be drawn.
    • Lines 10,11: Dimensions of the rectangle where the lines are to be drawn.
    • Line 12: GPU for drawing lines.
    • Line 13: LPU for drawing line.
    • Line 14: Line length.

⬛ Private Methods
Following is a discussion of the helper methods in the LineGeneratorTest class.

🔲 private void validateLength( Iterator<Line2D> lineIter, double expLen )
This method verifies that every line produced by a Line2D iterator, such as that provided by LineGenerator.axesIterator, produces lines of the expected length (expLen). The conditional logic at lines 11-19 below determines whether the generated line is horizontal or vertical, then determines its length based on the coordinates of its endpoints. The assertion at line 22 verifies that the iterator generated at least one line.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void validateLength( Iterator<Line2D> lineIter, double expLen )
{
    int count   = 0;
    while ( lineIter.hasNext() )
    {
        ++count;
        Line2D  line    = lineIter.next();
        Point2D point1  = line.getP1();
        Point2D point2  = line.getP2();
        double  actLen  = 0;
        if ( point1.getX() == point2.getX() )
        {
            actLen = point2.getY() - point1.getY();
        }
        else
        {
            assertEquals( point1.getY(), point2.getY() );
            actLen = point2.getX() - point1.getX();
        }
        assertEquals( expLen, actLen );
    };
    assertTrue( count > 0 );
}

🔲 private void validateOrientation( LineGenerator lineGen, int orientation )
This method verifies that every line produced by a given LineGenerator produces a line in the given orientation, HORIZONTAL and/or VERTICAL. Here’s the code.

private void 
validateOrientation( LineGenerator lineGen, int orientation )
{
    boolean             actHasHor   = false;
    boolean             actHasVert  = false;
    Iterator<Line2D>    iter        = lineGen.iterator();
    while ( iter.hasNext() )
    {
        Line2D  line    = iter.next();
        Point2D point1  = line.getP1();
        Point2D point2  = line.getP2();
        if ( point1.getX() == point2.getX() )
            actHasVert = true;
        else if ( point1.getY() == point2.getY() )
            actHasHor = true;
        else
            fail( "Strange orientation" );
    }
    
    boolean expHasHor   = 
        (orientation & LineGenerator.HORIZONTAL) != 0;
    boolean expHasVert  = 
        (orientation & LineGenerator.VERTICAL) != 0;
    assertEquals( expHasHor, actHasHor );
    assertEquals( expHasVert, actHasVert );
}

🔲 private void validateAxes( Iterator<Line2D> iter )
Verifies that the Line2D iterator provided by LineGenerator.axesIterator() correctly produces the x- and y-axes. Here is an annotated listing.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private void validateAxes( Iterator<Line2D> iter )
{
    double  centerY = testRect.getCenterY();
    Point2D xco1    = 
        new Point2D.Double( testRect.getMinX(), centerY );
    Point2D xco2    = 
        new Point2D.Double( testRect.getMaxX(), centerY );
    Line2D  xAxis   = new Line2D.Double( xco1, xco2 );

    double  centerX = testRect.getCenterX();
    Point2D yco1    = 
        new Point2D.Double( centerX, testRect.getMinY() );
    Point2D yco2    = 
        new Point2D.Double( centerX, testRect.getMaxY() );
    Line2D  yAxis   = new Line2D.Double( yco1, yco2 );
    
    Comparator<Line2D>  comp    = 
        (l1, l2) -> (int)(l1.getX1() - l1.getX2());
    List<Line2D> expSet  = new ArrayList<>();
    expSet.add( xAxis );
    expSet.add( yAxis );
    expSet.sort( comp );
    
    List<Line2D> actSet  = new ArrayList<>();
    actSet.add( iter.next() );
    actSet.add( iter.next() );
    assertFalse( iter.hasNext() );
    actSet.sort( comp );
    
    assertLineEquals( expSet.get( 0 ), actSet.get( 0 ) );
    assertLineEquals( expSet.get( 1 ), actSet.get( 1 ) );
}
  • Line 3-8: Generate the expected coordinates of the x-axis:
    • Line 3: Get the y-coordinate of the x-axis.
    • Lines 4,5: Calculate the left endpoint of the x-axis.
    • Lines 6,7: Calculate the right endpoint of the x-axis.
    • Line 8: Instantiate a line corresponding to the x-axis.
  • Line 10-15: Generate the expected coordinates of the y-axis:
    • Line 10: Get the x-coordinate of the y-axis.
    • Lines 11,12: Calculate the top endpoint of the y-axis.
    • Lines 13,14: Calculate the bottom endpoint of the y-axis.
    • Line 15: Instantiate a line corresponding to the y-axis.
  • Lines 17,18: Create a Comparator for two Line2Ds based on the length of the lines. The line with the shorter length is less than the line with the longer length.
  • Lines 18-21: Create a list of the lines we expect to be generated by the iterator.
  • Line 22: Sort the list of expected lines.
  • Lines 24-26: Create a list of the lines actually generated by the iterator.
  • Line 27: Verify that the iterator produced exactly two lines.
  • Line 28: Sort the list of actual lines.
  • Lines 30-31: Verify that the expected list of lines equals the actual list.

🔲 private void assertLineEquals( Line2D expLine, Line2D actLine )
This method verifies that a given line has the expected coordinates. Here’s a listing.

private void assertLineEquals( Line2D expLine, Line2D actLine )
{
    assertEquals( expLine.getX1(), actLine.getX1() );
    assertEquals( expLine.getX2(), actLine.getX2() );
    assertEquals( expLine.getY1(), actLine.getY1() );
    assertEquals( expLine.getY2(), actLine.getY2() );
}

🔲 private float calcFromExpCount( int expCount )
Calculate the width or height of a rectangle so that a) the rectangle will contain the given number of lines (expCount), and b) no line will fall on the edge of the rectangle. Here’s an example of how it’s used:

@Test
public void testNewLineGenRectangle2DFloatFloatFloat()
{
    int     expHorCount     = 4;
    int     expVertCount    = 8;
    testWidth = calcFromExpCount( expVertCount );
    testHeight = calcFromExpCount( expHorCount );
    makeRect();
    // ...
}

The code looks like this:

private float calcFromExpCount( int expCount )
{
    testGPU = baseGPU;
    testLPU = 1;
    float   dim = expCount * (1.1f * testGPU);
    return dim;
}

🔲 private void makeRect()
The makeRect method instantiates the test rectangle based on the default x- and y-coordinates and the current width and height test parameters. The method is listed below.

private void makeRect()
{
    testRect = new Rectangle2D.Double( 
        baseRectXco, 
        baseRectYco, 
        testWidth, 
        testHeight
    );
}

🔲 private Line2D getVertLine( double xOffset )
Given the current test parameters (testRect, testLen), generate a vertical line centered on the x-axis at a given offset (xOffset) from the y-axis. The annotated code follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
private Line2D getVertLine( double xOffset )
{
    double  centerX = testRect.getCenterX();
    double  centerY = testRect.getCenterY();
    double  xco     = centerX + xOffset;
    double  halfLen = testLen / 2;
    double  yco1    = centerY - halfLen;
    double  yco2    = centerY + halfLen;
    Point2D point1  = new Point2D.Double( xco, yco1 );
    Point2D point2  = new Point2D.Double( xco, yco2 );
    Line2D  line    = new Line2D.Double( point1, point2 );
    return line;
}
  • Line 3: Get the x-coordinate of the y-axis.
  • Line 4: Get the y-coordinate of the x-axis.
  • Line 5: Get the x-coordinate of the target line.
  • Lines 6-8: Get two y-coordinates, one-half the length of the target line above the x-axis and one-half the length below the x-axis.
  • Lines 9-12: Instantiate and return the target line.

🔲 private Line2D getHorLine( double yOffset )
Given the current test parameters (testRect, testLen), generate a horizontal line centered on the y-axis at a given offset (yOffset) from the x-axis. This method is very similar to getVertLine; its implementation is an exercise for the student, and the solution can be found in the GitHub repository.

⬛ JUnit @BeforeEach Method
This method is executed immediately before the start of every test method. It initializes test parameters to their default values. It is shown below.

@BeforeEach
public void setUp() throws Exception
{
    testWidth = baseWidth;
    testHeight = baseHeight;
    testGPU = baseGPU;
    testLPU = baseLPU;
    testLen = baseLen;
    makeRect();
}

Test Methods

Following is a description of the test methods (those marked @Test) in the LineGeneratorTest class.

@Test public void testNewLineGenRectangle2DFloatFloat()
This method validates the constructor to which we pass the rectangle to contain our drawing, the grid unit (pixels per unit), and the lines per unit. In this test, we will verify that the object produced by the constructor can correctly generate the axes and horizontal and vertical lines of the correct length. Refer to the figure below.

In lines 4-7, we create the rectangle to pass to the constructor (see makeRect). In lines 10-12, we verify that the LineGenerator correctly produces the axes, nonaxial lines of correct length, and both horizontal and vertical lines (see validateAxes, validateLength, and validateOrientation).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Test
public void testNewLineGenRectangle2DFloatFloat()
{
    float   dim     = 300;
    testWidth = dim;
    testHeight = dim;
    makeRect();
    LineGenerator   lineGen = 
        new LineGenerator( testRect, testGPU, testLPU );
    validateAxes( lineGen.axesIterator() );
    validateLength( lineGen.iterator(), dim );
    validateOrientation( lineGen, LineGenerator.BOTH );
}

@Test public void testNewLineGenRectangle2DFloatFloatFloat()
This method validates the constructor to which we pass the rectangle to contain our drawing, the grid unit (pixels per unit), the lines per unit, and the line length. As in the previous test (testNewLineGenRectangle2DFloatFloat ), we wish to verify that the object produced by the constructor can correctly generate the axes and horizontal and vertical lines of the correct length. We will also verify that the LineGenerator object produces the correct number of horizontal and vertical lines. We execute the test 48 times, with a different length each time. The code looks like this.

@Test
public void testNewLineGenRectangle2DFloatFloatFloat()
{
    int     expHorCount     = 4;
    int     expVertCount    = 8;
    testWidth = calcFromExpCount( expVertCount );
    testHeight = calcFromExpCount( expHorCount );

    makeRect();
    for ( int len = 2 ; len < 50 ; ++len )
    {
        LineGenerator   lineGen = 
            new LineGenerator( testRect, testGPU, testLPU, len );
        assertEquals( expHorCount, lineGen.getHorLineCount() );
        assertEquals( expVertCount, lineGen.getVertLineCount() );
        validateAxes( lineGen.axesIterator() );
        validateLength( lineGen.iterator(), len );
        validateOrientation( lineGen, LineGenerator.BOTH );
    }
}

@Test public void testNewLineGenRectangle2DFloatFloatMinus1()
This test is very similar to testNewLineGenRectangle2DFloatFloatFloat, but this time, we pass a length of -1. We wish to verify that the LineGenerator produces horizontal and vertical lines whose length spans the test rectangle’s length and height. Here’s the code.

@Test
public void testNewLineGenRectangle2DFloatFloatMinus1()
{
    int     expCount    = 4;
    float   dim         = calcFromExpCount( expCount );

    testWidth = dim;
    testHeight = dim;
    makeRect();
    LineGenerator   lineGen = 
        new LineGenerator( testRect, testGPU, testLPU, -1 );
    assertEquals( expCount, lineGen.getHorLineCount() );
    assertEquals( expCount, lineGen.getVertLineCount() );
    validateAxes( lineGen.axesIterator() );
    validateLength( lineGen.iterator(), dim );
    validateOrientation( lineGen, LineGenerator.BOTH );
}

@ParameterizedTest public void
testNewLineGenRectangle2DFloatFloatFloatInt( int orientation )
This method exercises the “big” constructor, specifying all the line generation parameters. It’s a parameterized test that executes three times, one for each possible orientation parameter: HORIZONTAL, VERTICAL, and BOTH.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void 
testNewLineGenRectangle2DFloatFloatFloatInt( int orientation )
{
    int     expHorCount     = 4;
    int     expVertCount    = 8;
    testWidth  = calcFromExpCount( expVertCount );
    testHeight = calcFromExpCount( expHorCount );

    makeRect();
    LineGenerator   lineGen = new LineGenerator(
        testRect, 
        testGPU, 
        testLPU, 
        testLen, 
        orientation 
    );
    assertEquals( expHorCount, lineGen.getHorLineCount() );
    assertEquals( expVertCount, lineGen.getVertLineCount() );
    validateAxes( lineGen.axesIterator() );
    validateLength( lineGen.iterator(), testLen );
    validateOrientation( lineGen, orientation );
}

@ParameterizedTest public void
testNewLineGenRectangle2DFloatFloatMinus1Int( int orientation )
This method is virtually the same as testNewLineGenRectangle2DFloatFloatFloatInt. The main difference is that we pass -1 for the constructor’s length parameter and verify that it produces lines that span the width and height of the enclosing rectangle. A listing of this method follows.

@ParameterizedTest
@ValueSource( ints= {
    LineGenerator.HORIZONTAL, 
    LineGenerator.VERTICAL,
    LineGenerator.BOTH
})
public void 
testNewLineGenRectangle2DFloatFloatMinus1Int( int orientation )
{
    int     expCount    = 4;
    float   dim         = calcFromExpCount( expCount );
    testWidth  = dim;
    testHeight = dim;

    makeRect();
    LineGenerator   lineGen = new LineGenerator(
        testRect, 
        testGPU, 
        testLPU, 
        -1, 
        orientation 
    );
    assertEquals( expCount, lineGen.getHorLineCount() );
    assertEquals( expCount, lineGen.getVertLineCount() );
    validateAxes( lineGen.axesIterator() );
    validateLength( lineGen.iterator(), dim );
    validateOrientation( lineGen, orientation );
}

@Test public void testAxesIterator()
This method validates the LineGenerator’s axesIterator() class method overload. The code follows. See also validateAxes.

@Test
public void testAxesIterator()
{
    LineGenerator   lineGen = 
        new LineGenerator( testRect, testGPU, testLPU );
    validateAxes( lineGen.axesIterator() );
}

@Test public void testAxesIteratorRectangle2D()
This method validates the LineGenerator’s axesIterator(Rectangle2D) class method overload. The code follows. See also validateAxes.

@Test
public void testAxesIteratorRectangle2D()
{
    Iterator<Line2D>    iter    =
        LineGenerator.axesIterator( testRect );
    validateAxes( iter );
}

@Test public void testIterator()
This method validates the LineGenerator’s iterator method. It establishes a test rectangle and calculates the position of every expected horizontal and vertical line to be drawn within it. Then, it generates actual horizontal and vertical lines using a LineGenerator object and compares them to the expected lines. The annotated code follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Test
public void testIterator()
{
    int     expHorCount     = 4;
    int     expVertCount    = 8;
    testWidth = calcFromExpCount( expVertCount );
    testHeight = calcFromExpCount( expHorCount );
    makeRect();
    LineGenerator   lineGen = 
        new LineGenerator( testRect, testGPU, testLPU, baseLen );


    List<Line2D>    expList = new ArrayList<Line2D>();  
    int     halfHorCount    = expHorCount / 2;
    IntStream.rangeClosed( -halfHorCount, halfHorCount)
        .filter( i -> i != 0 )
        .mapToDouble( i -> i * testGPU )
        .mapToObj( this::getHorLine )
        .forEach( expList::add );
    
    int     halfVertCount   = expVertCount / 2;
    IntStream.rangeClosed( -halfVertCount, halfVertCount)
        .filter( i -> i != 0 )
        .mapToDouble( i -> i * testGPU )
        .mapToObj( this::getVertLine )
        .forEach( expList::add );

    List<Line2D>    actList = new ArrayList<Line2D>(); 
    lineGen.forEach( actList::add );
    
    Comparator<Line2D>  comp    = 
        (l1, l2) -> l1.getX1() == l1.getX2() ? 1 : 0;
    expList.sort( comp );
    actList.sort( comp );
    int             expSize = expList.size();
    assertEquals( expSize, actList.size() );
    IntStream.range( 0, expSize )
        .forEach( i -> 
            assertLineEquals( expList.get( i ), actList.get( i ) )
        );
    
}
  • Lines 4,5: Establish the number of vertical and horizontal lines we want to be generated by a LineGenerator iterator.
  • Lines 6-8: Calculate the width and height for the test rectangle. These values will be the correct size to contain the desired number of horizontal and vertical lines while ensuring that no line falls on the edge of the rectangle; see calcFromExpCount.
  • Line 13: Create a list to hold the expected lines.
  • Line 14: Calculate half the expected horizontal line count (half the lines will be above and x-axis and half below).
  • Lines 15-19: Generate the expected horizontal lines:
    • Line 16: Don’t expect a horizontal line to be generated at the origin.
    • Line 17: Calculate the y-coordinate of the expected horizontal line; note that half the y-coordinates will be negative.
    • Line 18: Get the horizontal line at the given y-coordinate; see getHorLine.
    • Line 19: Add the line to the expected line list.
  • Lines 21-26: Calculate the expected vertical lines using an algorithm similar to the one to generate horizontal lines (lines 15-19).
  • Lines 28,29: Use the LineGenerator (lines 9,10) to compile an actual list of lines.
  • Lines 31,32: Create a comparator to sort the expected and actual line lists. The comparator will position all horizontal lines before all vertical lines.
  • Lines 33,34: Sort the expected and actual line lists.
  • Lines 35,36: Verify that the lists contain the same number of lines.
  • Lines 37-40: Verify that the corresponding lines in the two lists are equal.

@Test public void testGetHorLineCount()
@Test public void testGetVertLineCount()
These tests validate the LineGenerator’s testGetHorLineCount and testGetVertLineCount methods. Following is the annotated listing for testGetHorLineCount . The implementation of testGetVertLineCount is an exercise for the student; the solution can be found in the GitHub repository.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void testGetHorLineCount()
{
    testLPU = 2f;
    String  comment = 
        "GPU=" + testGPU + ",LPU=" + testLPU + ",Height=";
    for ( float height = 10 ; height < 500 ; ++height )
    {
        testHeight = height;
        makeRect();
        LineGenerator   lineGen = 
            new LineGenerator( testRect, testGPU, testLPU );
        float   halfHeight  = height / 2;
        int     halfCount   = (int)((halfHeight / testGPU) * testLPU );
        if ( (height % testGPU) == 0 )
            --halfCount;
        int     expCount    = halfCount * 2;
        int     actCount    = (int)lineGen.getHorLineCount();
        assertEquals( expCount, actCount, comment + height );
    }
}
  • Line 4: Set the test LPU parameter to 2 (any integer greater than 1 will do).
  • Line 5,6: Create a comment that will be used to improve the assertion output at line 19.
  • Lines 7-20: Using rectangles with a width between 10 to 500 pixels in height.
    • Lines 9,10: Create a test rectangle of the desired height.
    • Lines 11,12: Create a LineGenerator.
    • Lines 13-15: Calculate the expected number of horizontal lines for half the rectangle. If the rectangle’s height is a multiple of the test GPU parameter, there will be one line less than the count calculated in line 14.
    • Line 16: The count calculated above was for half the rectangle; refine the count to reflect the complete rectangle.
    • Lines 17,18: Get the actual count from the LineGenerator and compare it to the expected count.

Code Coverage

LineGeneratorTest gives us 100% code coverage on the LineGenerator class.

Summary

So, in summary, what are some of the major takeaways from this lesson?

  1. Unit testing is the responsibility of the developer, not the test group. Having said that, I would encourage the developer to work closely with the test group. If the developer knows what and how the test group is performing their tests, the developer might be able to make their job easier by some painless code restructuring. And the test group might very well have tools that the developer would find helpful in unit testing and/or debugging.
  2. Unit testing is an integral part of the development process. In fact, some strategies call for writing unit tests before writing any of the code they’re intended to validate. See:
  3. If a project manager asks you for a time estimate for developing a particular piece of code, your estimate should include the time it will take to develop the unit tests, which, as you can see from this lesson, can be a significant effort.
  4. Unit testing targets the public elements of your code. Students often ask me, “But how am I supposed to test my private methods?” My answer to that is always:
    • Are you really talking about unit testing, or are you talking about debugging? If you’re debugging, you have several strategies unavailable for unit testing, including temporarily modifying the code to investigate particular aspects of the problem.
    • You should be able to reach the code for all of your private methods by testing your public methods (yes, there are occasional, infrequent exceptions to this). If you can’t reach the code in your private methods via your public methods ask yourself: why is that private method there?
  5.  JUnit is a terrific tool for developing unit tests. Use it. Study it. Read a textbook or take a tutorial more focused on JUnit than this one. See the list of recommended resources at the top of the previous page.
  6. Unit test code is “real” code. If your organization has coding standards, they should apply to your unit test code. Take as much care with your test code as you do with your production code; to paraphrase the punchline to the joke about getting to Carnegie Hall, encapsulate, encapsulate, encapsulate. Your unit tests should be under source control, and your unit test code has to be updated when the associated production source code is changed.

Next: The Cartesian Plane Project, Property Management