Version: 5.2.1

Classes with Containment

Designing tests for simple classes and classes with containment

The following simplified class diagram represents a circle we may want to draw on some canvas. The circle location is represented by its own class, as we think that later we may deal with other shapes that also refer to their location:

 

+------------+

| Circle     |

+------------+

| Loc center |--+

| int radius |  |

+------------+  |

                v

            +-------+

            | Loc   |

            +-------+

            | int x |

            | int y |

            +-------+

Here are the class definitions and examples of data:

// to represent a circle on a canvas

class Circle{

  Loc center;

  int radius;

 

  Circle(Loc center, int radius){

    this.center = center;

    this.radius = radius;

  }

}

 

// to represent a location on a canvas

class Loc{

  int x;

  int y;

 

  Loc(int x, int y){

    this.x = x;

    this.y = y;

  }

}

 

// examples for Loc and Circle classes

class Examples{

  Examples(){}

 

  Loc loc34 = new Loc(3, 4);

  Loc loc57 = new Loc(5, 7);

 

  Circle cir34r10 = new Circle(this.loc34, 10);

  Circle cir57r5 = new Circle(this.loc57, 5);

  Circle cir57r10 = new Circle(this.loc57, 10);

 

  // run the tests, show all data

  public static void main(String[] argv){

    ExamplesCircle e = new ExamplesCircle();

    Tester.runFullReport(e);

  }

}

Running the program produces the following output:

Tester Prima v.1.5.1 - 17 June 2012

-----------------------------------

Tests defined in the class: ExamplesCircle:

---------------------------

ExamplesCircle:

---------------

 

 new ExamplesCircle:1(

  this.loc34 =

   new Loc:2(

    this.x = 3

    this.y = 4)

  this.loc57 =

   new Loc:3(

    this.x = 5

    this.y = 7)

  this.cir34r10 =

   new Circle:4(

    this.center = Loc:2

    this.radius = 10)

  this.cir57r5 =

   new Circle:5(

    this.center = Loc:3

    this.radius = 5)

  this.cir57r10 =

   new Circle:6(

    this.center = Loc:3

    this.radius = 10))

---------------

No test methods found.

We now want to design several methods:

// in the class Loc:

// produce from this location a new one moved by the the given dx, dy

Loc moveBy(int dx, int dy){...}

 

// in the class Circle:

// produce a new Circle from this one with the radius doubled

Circle doubleRadius(){...}

 

// in the class Circle:

// produce from this circle one with the location moved by the given dx, dy

Circle moveBy(int dx, int dy){...}

Test-First Design requires that we make examples first:

// test the method moveBy in the class Loc

void testMoveByLoc(Tester t){

  t.checkExpect(this.loc34.moveBy(2, 3), this.loc57);

}

 

// test the method moveBy in the class Circle

void testDoubleRadius(Tester t){

  t.checkExpect(this.cir57r5.doubleRadius(), cir57r10);

  t.checkExpect(this.cir34r10.doubleRadius(), cir57r10);

}

 

// test the method moveBy in the class Circle

void testMoveByCircle(Tester t){

  t.checkExpect(this.cir34r10.moveBy(2, 3), cir57r10);

}

We now design the methods;

//produce from this location a new one moved by the the given dx, dy

Loc moveBy(int dx, int dy){

  return new Loc(this.x + dx, this.y + dy);

}

 

//produce a new Circle from this one with the radius doubled

Circle doubleRadius(){

  return new Circle(this.center, 2 * this.radius);

}

 

//produce from this circle one with the location moved by the given dx, dy

Circle moveBy(int dx, int dy){

  return new Circle(this.center.moveBy(dx,  dy), this.radius);

}

When we run the full report, we get the following results (after the data display):

 

Ran 4 tests.

1 test failed.

 

Full test results:

-------------------

 

Success in the test number 1

 

actual:                                 expected:

 new Loc:1(                                        new Loc:1(

  this.x = 5                                        this.x = 5

  this.y = 7)                                       this.y = 7)

 

 

 

Success in the test number 2

 

actual:                                 expected:

 new Circle:1(                                     new Circle:1(

  this.center =                                     this.center =

   new Loc:2(                                        new Loc:2(

    this.x = 5                                        this.x = 5

    this.y = 7)                                       this.y = 7)

  this.radius = 10)                                 this.radius = 10)

 

 

 

Error in test number 3

 

tester.ErrorReport: Error trace:

        at ExamplesCircle.testDoubleRadius(Circle.java:60)

        at ExamplesCircle.main(Circle.java:71)

actual:                                 expected:

 new Circle:1(                                     new Circle:1(

  this.center =                                     this.center =

   new Loc:2(                                        new Loc:2(

    this.x = 3....................................    this.x = 5

    this.y = 4)                                       this.y = 7)

  this.radius = 20)                                 this.radius = 10)

 

 

 

Success in the test number 4

 

actual:                                 expected:

 new Circle:1(                                     new Circle:1(

  this.center =                                     this.center =

   new Loc:2(                                        new Loc:2(

    this.x = 5                                        this.x = 5

    this.y = 7)                                       this.y = 7)

  this.radius = 10)                                 this.radius = 10)

 

 

 

--- END OF FULL TEST RESULTS ---

We see that the failed test report includes the link to the test itself and it marks the first place where the actual and expected values differ.

Notice, that the tester library compares the values of the objects and the programmer does not have to build the mental model of the object representation in the memory.

_________________________________________________________________________________

Testing void methods

Writing methods in the mutation-free style makes the test design easy. If we wanted to rewrite these method so that the locations and the circles were changed as an effect of the methods, the code for the method bodies would seem to be simpler, but the tests become more complex.

Here is the complete program:

import tester.*;

 

// to represent a circle on a canvas

class Circle{

  Loc center;

  int radius;

 

  Circle(Loc center, int radius){

    this.center = center;

    this.radius = radius;

  }

 

  //EFFECT: double the radius of this circle

  void doubleRadius(){

    this.radius = 2 * this.radius;

  }

 

  //EFFECT: move the center of this circle by the given dx, dy

  void moveBy(int dx, int dy){

    this.center.moveBy(dx,  dy);

  }

}

 

// to represent a location on a canvas

class Loc{

  int x;

  int y;

 

  Loc(int x, int y){

    this.x = x;

    this.y = y;

  }

 

  //EFFECT: move this location by the given dx, dy

  void moveBy(int dx, int dy){

    this.x = this.x + dx;

    this.y = this.y + dy;

  }

 

}

 

// examples for Loc and Circle classes

class ExamplesCircle{

  ExamplesCircle(){}

 

  Loc loc34 = new Loc(3, 4);

  Loc loc57 = new Loc(5, 7);

 

  Circle cir34r10 = new Circle(this.loc34, 10);

  Circle cir57r5 = new Circle(this.loc57, 5);

  Circle cir57r10 = new Circle(this.loc57, 10);

 

  // test the method moveBy in the class Loc

  void testMoveByLoc(Tester t){

    this.loc34.moveBy(2, 3);

    t.checkExpect(this.loc34, this.loc57);

  }

 

  // test the method moveBy in the class Circle

  void testDoubleRadius(Tester t){

    this.cir57r5.doubleRadius();

    t.checkExpect(this.cir57r5, cir57r10);

  }

 

  // test the method moveBy in the class Circle

  void testMoveByCircle(Tester t){

    this.cir34r10.moveBy(2, 3);

    t.checkExpect(this.cir34r10, cir57r10);

  }

 

  // run the tests, show all data

  public static void main(String[] argv){

    ExamplesCircle e = new ExamplesCircle();

    Tester.runFullReport(e);

  }

}

Unfortunately, our third test fails:

Ran 3 tests.

1 test failed.

 

Full test results:

-------------------

 

Success in the test number 1

 

actual:                                 expected:

 new Loc:1(                                        new Loc:1(

  this.x = 5                                        this.x = 5

  this.y = 7)                                       this.y = 7)

 

 

 

Success in the test number 2

 

actual:                                 expected:

 new Circle:1(                                     new Circle:1(

  this.center =                                     this.center =

   new Loc:2(                                        new Loc:2(

    this.x = 5                                        this.x = 5

    this.y = 7)                                       this.y = 7)

  this.radius = 10)                                 this.radius = 10)

 

 

 

Error in test number 3

 

tester.ErrorReport: Error trace:

        at ExamplesCircle.testMoveByCircle(Circle.java:68)

        at ExamplesCircle.main(Circle.java:74)

actual:                                 expected:

 new Circle:1(                                     new Circle:1(

  this.center =                                     this.center =

   new Loc:2(                                        new Loc:2(

    this.x = 7....................................    this.x = 5

    this.y = 10)                                      this.y = 7)

  this.radius = 10)                                 this.radius = 10)

 

 

 

--- END OF FULL TEST RESULTS ---

So, we now need to worry about the changes in the program caused by other methods, worry about the order of the execution. It is no longer sufficient to look at each method and its invocation separately — the whole program has to be understood at all times. And, of course, the tests have to be designed carefully, making sure that the changes that occur in one test case do not affect the result of another test.

Note: The tester library does not guarantee that the test methods will be executed in the order in which they were defined in the Examples... class, and so every test method must be designed to run independently of the other tests.

For a novice programmer we suggest the following organization of the code in the Examples... class:

// examples for Loc and Circle classes

class ExamplesCircle{

  ExamplesCircle(){}

 

  Loc loc34 = new Loc(3, 4);

  Loc loc57 = new Loc(5, 7);

 

  Circle cir34r10 = new Circle(this.loc34, 10);

  Circle cir57r5 = new Circle(this.loc57, 5);

  Circle cir57r10 = new Circle(this.loc57, 10);

 

  void reset(){

    this.loc34 = new Loc(3, 4);

    this.loc57 = new Loc(5, 7);

 

    this.cir34r10 = new Circle(this.loc34, 10);

    this.cir57r5 = new Circle(this.loc57, 5);

    this.cir57r10 = new Circle(this.loc57, 10);

  }

 

  // test the method moveBy in the class Loc

  void testMoveByLoc(Tester t){

    this.reset();

    this.loc34.moveBy(2, 3);

    t.checkExpect(this.loc34, this.loc57);

  }

 

  // test the method moveBy in the class Circle

  void testDoubleRadius(Tester t){

    this.reset();

    this.cir57r5.doubleRadius();

    t.checkExpect(this.cir57r5, cir57r10);

  }

 

  // test the method moveBy in the class Circle

  void testMoveByCircle(Tester t){

    this.reset();

    this.cir34r10.moveBy(2, 3);

    t.checkExpect(this.cir34r10, cir57r10);

  }

 

  // run the tests, show all data

  public static void main(String[] argv){

    ExamplesCircle e = new ExamplesCircle();

    Tester.runFullReport(e);

  }

}