Unit testing needs to be sorted out before implementing any classes, even if writing actual tests isn't going to start yet. IDEs or unit testing frameworks such as JUnit can be used (and it is a good idea to look into them for ideas, at least). But what is the simplest approach that works properly?
For small classes, testing can be done in a main method in the
class itself. Java's assertions could be used, but are complicated by the need
for the -ea flag. So it seems simpler to avoid assertions and
define classes like this:
class X {
static void claim(boolean b) { if (!b) throw new Error("Bug"); }
public static void main(String[] args) {
claim(2+2 == 4);
System.out.println("Class X OK");
}
}
The claim method is roughly equivalent to Java's
assert keyword. This is all that is necessary for the current
project.
This simple approach to testing can be developed further in various ways.
For example, rather than call claim(s.equals("x")), it might be
preferable to use claimEq(s, "x"). The claimEq method
can be written:
static void claimEq(Object x, Object y) {
if (x == y) return;
if (x != null && x.equals(y)) return;
throw new Error("Bug");
}
Because of the boxing/unboxing feature of Java, this works for primitive
types, e.g. integers and characters, as well as objects which have a suitable
equals method. (However, as it stands, it doesn't work for
arrays).
If you do want to use Java's assertions, you can test whether they have been switched on like this:
...
public static void main(String[] args) {
boolean testing = false;
assert(testing = true);
if (! testing) System.err.println("You forgot the -ea flag");
...
}
Note the argument to the assert call is an assignment testing =
true not a comparison testing == true.
The main method approach to unit testing can also be scaled up
to large projects, by defining classes something like this:
class X implements Testable {
public String test(String in) { ... }
public static void main(String[] args) {
Test.run(args, new X());
}
}
The Testable interface describes a test method
which takes a test expressed as a string and returns a string result which can
be checked against what is expected. The test method may be
effectively static, creating any objects it needs, but is not declared
static, so that it can be described by the interface.
Shared test code is put into a general Test class, which doesn't
depend directly on any of the project classes, only on the Testable
interface, so that a class can be tested when other classes that it doesn't
depend on are broken. The run method has the signature:
static run(String[] args, Testable sample) { ... }
The object passed as the second argument is just a sample object used by the
Test class to access the test method. The battery of
tests for a class is put into a text file, which is read and interpreted by the
Test class.