Guide to Testing (without Boxes)


Step one: Using the testing library

In order to test without using the graphical boxes, we will be using a testing library.

On the lab computers, this library is already installed into DrScheme (Reminder, use the drscheme located at /home/kathyg/plt/bin/drscheme). For your personal computers, download the following file  --- testing.plt --- Open DrScheme, select File -> Install .plt file. Select the location of the testing.plt file you download and follow the dialogues to install the library. Then quit and restart DrScheme. If you receive any error messages durring this process, you may need to install a more recent DrScheme, please see pre.plt-scheme.org/installers If you continue to receive error messages, please report them to me and I will asssist you.

To use the testing library to test your programs, ProfessorJ needs to know about the library. At the top of your file, enter the following:

import testing.*;

For your homeworks, this line should come after the comments including your name.

This import allows your program to use the testing mechanisms described below. This is also the means of importing other libraries in Java, which we will discuss in more detail later in the course.

Step two: Create a program to test

On the lines following the import, write your program following the Design Recipe, up to the Test step. An example program is included below.

import testing.*;

class Painting {

    String painter;
    String title;
    double value;
    boolean forSale;

    Painting( String painter, String title, double value, boolean forSale ) {
        this.painter = painter;
        this.title = title;
        this.value = value;
        this.forSale = forSale;
     }

     //new Painting("Picasso","Starry Night",1e10, false) -> 4e10
     //new Painting("GirlNextDoor","My Dog",4.50,true) -> 4.50
     //To report the price to buy this Painting, any owner will sell if the price is high enough
     double price() {
         // ... this.painter ... this.title ... this.value ... this.forSale ...
         if (this.forSale)
             return this.value;
         else
             return this.value * 4;
     }

     //new Painting("Opra","Self Portrait",10000,true).mistkenIdentification("Shelly","Opra") ->
     //    new Painting("Shelly","Opra",10000,true)
     //To produce a new painting, when the painter was misidentified originally
     Painting mistakenIdentification( String truePainter, String trueTitle ) {
         return new Painting(truePainter,trueTitle, this.value, this.forSale );
     }

}  


Step 3: Adding a test class

Within this system, tests are contained within their own class -- one test class per problem solving class (Painting, Fish, and Fisherman are all problem solving classes). The test class should appear after the class it tests, for clarity of reading. Further, the name of the test class should indicate which class is being tested. For example, the test class for a Painting will be called PaintingTest. (Note that PaintingTest must have a constructor)

class PaintingTest {
    PaintingTest() { }
}

The test class must extend Test, which comes from the testing library. This extends is similar to those we have used to create conditional compound data, in that PaintingTest is a kind of Test. The extension provides testing functionality within PaintingTest.

class PaintingTest extends Test {
    PaintingTest() { }
}

Step 4: Adding a test

We are now ready to add our first actual test. We will test the price() method of the Painting class, using our examples. To begin, we add a mthod to PaintingTest. This method must return a TestResult and needs no external information (i.e. no arguments). The name of the method must begin with the word 'test' and should provide information regarding the test preformed. So for the first example to price, the method's header is --- TestResult testPriceNotForSale()


class PaintingTest extends Test {
   PaintingTest() { }

   TestResult testPriceNotForSale() {
   }

Do not worry about a purpose, template, examples, or tests for testPriceNotForSale -- test methods do not follow the Design Recipe, they follow the Test Guide.

Step 4b: Making the test's body

In translating the example into a testcase, you will use the method call and the expected result of the method. These must be compared. The Test class, and consequently any extension of Test, contains several methods that properly compare two values.  A full list follows at the end of this document.

For the first example of price case, we must compare two doubles. The result of calling price on 'new Painting("Picasso","Starry Night",1e10, false)' and 4e10, the expected answer. To compare two doubles, we use the compareInexacts method. This particular method takes three double arguments. The third allows the comparison to take into account rounding errors within Java arithmetic due to the representation of real numbers, use 0.01 for this argument.

The full method body for testPriceNotForSale is below

class PaintingTest extends Test {
   PaintingTest() { }

   TestResult testPriceNotForSale() {
      return this.compareInexacts(new Painting("Picasso","Starry Night",1e10, false).price(), 4e10, 0.01);
    }
}

All examples should be made into test cases in a similar style. The following box shows the full test class, including the comparison of two paintings. When comparing two paintings, we use the compareObjects method, that expects to see two instances of a class (i.e. new Painting(...) is an instance of a class)

class PaintingTest extends Test {
   PaintingTest() { }

   TestResult testPriceNotForSale() {
      return this.compareInexacts(new Painting("Picasso","Starry Night",1e10, false).price(), 4e10, 0.01);
    }

   TestResult testPriceForSale() {
      return this.compareInexacts(new Painting("GirlNextDoor","My Dog",4.50,true).price(), 4.50, 0.01);
    }

    TestResult testMistakenIdentification() {
        return this.compareObjects(new Painting("Opra","Self Portrait",10000,true).mistakenIdentification("Shelly","Opra") ,
                                                    new Painting("Shelly","Opra",10000,true) );
    }
}

Step 5: Running the tests

After writing the tests, it is important to run them and ensure that the program preforms as expected. To do this, hit the Run button, and then move to the Interactions window.

Within the Interactions window, enter
new PaintingTest().run()

You will see:

Ran Tests for PaintingTest : 100.0 % passed : ok!
TestSummary(
   classTested = "PaintingTest",
   numTests = 3,
   passedTests = 3,
   failedTests = 0)

if the tests all pass, and

Ran Tests for PaintingTest : 33.33333333333333 % passed : The following tests failed:
testMistakenIdentification ---
      expected Painting(painter = "Shelly", title = "Opra", value = 10000, forSale = true)
      actual   Painting(painter = "Shelly", title = "Ora", value = 10000, forSale = true)
testPriceForSale ---
      expected 4.5
      actual   4.05
TestSummary(
   classTested = "PaintingTest",
   numTests = 3,
   passedTests = 1,
   failedTests = 2) 

if one or more tests failed.

The top text is an overview of the tests that failed and the overall success rate. The bottom text is a TestSummary value containing four fields that record information about your tests.


Formal design of Test

Test

TestResult compareInexacts( double actual, double expected, double epsilon)
TestResult compareExacts(int actual, int expected)
TestResult compareStrings( String actual, String expected)
TestResult compareObjects( Object actual, Object expected)
TestResult compare( boolean actual, boolean expected)
TestSummary run()