On this page:
2.1 Getting started
2.1.1 Adding the tester library to your project
2.1.2 Using the tester library to run your project
2.2 Using the tester library
2.2.1 Naming test methods
2.2.2 Writing test methods with multiple tests
2.2.3 Writing multiple test methods
2.2.4 The testing methods
2.2.4.1 Basic methods:
2.2.4.2 Exception testing
2.2.4.3 Testing methods with randomized results:
2.2.4.4 Collection-based methods:
2.2.4.5 Miscellaneous methods:
2.2.5 How check  Expect compares for equality
8.13

The Tester Library🔗

Related files:
  tester.jar  

The tester library provides a mechanism for testing programs, checking that their results are as expected.

2.1 Getting started🔗
2.1.1 Adding the tester library to your project🔗

First download the tester library from the link above. Save it to your IntelliJ workspace directory, or somewhere convenient to find. Then, for each Java project you create:

These instructions are for IntelliJ; if you choose to use a different IDE, the details will be different but the main ideas are the same.

  1. Go to File in the top left of the page, and navigate to Project Structure. You could also get to Project Structure by right clicking on the project itself, in IntelliJ’s Project Explorer view

  2. In the list on the left, select the Libraries section

  3. In the top left of Libraries, click on New Project Library:

  4. The file chooser window will be shown. Navigate to where you saved the tester.jar file and select it.

  5. Hit Finish.

2.1.2 Using the tester library to run your project🔗
  1. Make sure your project is opened in the Project pane.

  2. In the Run menu select Edit Configurations. Equivalently, you can use the dropdown menu to the left of the Run toolbar button (which looks like a green "play" button):

  3. In the top left corner of the Run/Debug Configurations tab, click on the "+" button that should say Add New Configuration when your mouse hovers over. Then, choose Application.

  4. Choose a name for this configuration: usually you should choose the same as the name of your project, but you can make multiple run configurations, and so should give them different, memorable names.

  5. In the Build and run section click on the first entry box.

  6. Enter tester.Main verbatum.

  7. Click on the second entrybox in Build and run. In the Program arguments text field enter the name of your examples class.

  8. At the bottom of the Run/Debug Configurations select Apply then OK.

  9. To run the configuration, just click the "play" button on the toolbar. Be sure that your new run configuration is selected. It’s name should be what you have named it as.

2.2 Using the tester library🔗
2.2.1 Naming test methods🔗

The tester library expects a particular naming convention and signature for your test methods, or else it will not find them. Specifically, it is looking for:
  • Methods in the class whose name is specified in the run configuration,

  • whose name begins with the four letters test,

  • whose sole argument is a parameter of type Tester,

  • and whose return type is either boolean or void.

In other words, only the first of the signatures below will be run as a test case by the tester library:

class ExamplesTesting {
boolean testSomething(Tester t) { ... } // good!  
boolean tstTypos(Tester t) { ... } // wrong method name  
boolean dontTestThis(Tester t) { ... } // wrong method name  
boolean TestSomething(Tester t) // wrong method name  
boolean testSomething() { ... } // wrong signature  
boolean testSomething(Tester t, int anythingElse) { ... } // wrong signature  
int testSomething(Tester t) { ... } // wrong return type }
2.2.2 Writing test methods with multiple tests🔗

Frequently, you will want to write multiple tests within a single test method. Your test methods will probably look something like this:

class ExamplesTesting {
boolean testNumbers(Tester t) {
return t.checkExpect(2 + 2, 4) // this should pass && t.checkExpect(9000, 70) // this should fail && t.checkExpect(4, 4) // this should pass && t.checkExpect(4, 5); // this should fail }
}

Be careful! When you run this test method, you will not see four tests! Instead, you will see only one test passing, and one test failing. As soon as one test fails, because the code is combining the results with logical-and (&&), and logical operators short-circuit, the subsequent tests will not be run. Don’t be fooled into thinking you only have "just one failing test", if that test is in the middle of a block of other test cases.

Two alternate approaches are to separate each test into its own test method, or to separate the tests into individual statements. If you have not yet seen how to do the latter, then stick to the style above and just be careful.

2.2.3 Writing multiple test methods🔗

If you define multiple test methods in your examples class, e.g.
class ExamplesTesting {
boolean testScenario1(Tester t) {
return t.checkExpect(.......);
}
boolean testScenario2(Tester t) {
return t.checkExpect(.......);
}
boolean testScenario3(Tester t) {
return t.checkExpect(.......);
}
}

be aware that the tester library deliberately runs your tests in a random order every time. In other words, there is no guarantee that testScenario2 will run before testScenario3 or after testScenario1. This ensures that you’re not subtly relying on your tests running in a particular order relative to each other (especially when you rely on randomness or other mutable state) in order to pass. You need to ensure that your test cases are standalone, self-consistent, and can pass in whatever order they happen to run.

Within a single test method, tests will run in the sequence you wrote them in, like any Java method. The randomness described here is only at the level of independent test methods.

(This random-order behavior is standard across most testing frameworks, and is helpful for "keeping your code honest" about what is being tested.)

2.2.4 The testing methods🔗

The tester library exposes the following methods:

2.2.4.1 Basic methods:🔗
2.2.4.2 Exception testing🔗
2.2.4.3 Testing methods with randomized results:🔗

All the methods described above anticipate that the methods are deterministic. Sometimes, we need to test randomized methods instead, that might return one among several randomly chosen results.

2.2.4.4 Collection-based methods:🔗
2.2.4.5 Miscellaneous methods:🔗
2.2.5 How checkExpect compares for equality🔗

The checkExpect test uses a sameValue algorithm, that considers two values the same when:
  • Both values are null

  • Both values are identically the same object (using ==)

  • When both values are Strings, and the equals method returns true

  • When both values are boxed versions of primitive values (i.e. Integer, Double, Boolean, etc.), and their contained primitive values are equal

  • When both values are WorldScenes or WorldImages (see The Image Library for more information), and the scenes or images are constructed in the same manner: i.e., using the same overlays, besides, etc. to construct the intended image. It does not compare the scenes in terms of their final rendered pixel output.

  • When both values are arrays, and they have the same lengths, and the same values at the same indices (using sameValue recursively)

  • When both values are java.util.Sets, and they are set-equal as described in checkSet: i.e., using the equals method on the set values and not sameValue.

  • When both values are java.lang.Iterables, and they are iterable-equal as described in checkIterable: i.e., using sameValue to compare the sequence of values in order from both inputs.

  • When both values are java.lang.Maps, in which case they must have the same size, the same keys (using the containsKey operation on the Map), and corresponding values must be the same (using sameValue).

  • For everything else (including all user-defined classes), both values must be instances of the same class, and their fields must have the same values as compared using sameValue.

The tester library can handle cycles in the data being compared, and will not get stuck in an infinite loop. (If there are cycles that pass through a Array, Set or Map, however, then the equals methods must not get stuck in an infinite loop. However, if you are using such data structures rather than implementing your own, you are responsible for using them correctly.)