The Tcl code we will use is in the file javatute/test.tcl. The main part of this is the test procedure:
set TEST_PASSED 0
set TEST_FAILED 0
proc test {name description script answer} {
global TEST_PASSED TEST_FAILED
# Evaluate the script
puts "Test $name"
set code [catch {uplevel $script} result]
# Print info if it failed
if {$code != 0} {
puts "Description:"
puts " $description"
puts "Stack trace:"
jdkStackTrace
incr TEST_FAILED
} elseif {[string compare $result $answer] != 0} then {
puts "Description:\n $description"
puts "Expected result:\n $answer"
puts "Received result:\n $result"
incr TEST_FAILED
} else {
incr TEST_PASSED
}
update
}
After evaluating the script argument, this procedure prints
simple error diagnostics if the test threw an exception or produced
the wrong result.
To illustrate the use of the test procedure, here is a simple Java class, tutorial.tcltk98.BadPath, written (supposedly) for the purpose of constructing a filename path incrementally. It has two methods: append, which adds another element to the filename and getPath, which returns the path:
package tutorial.tcltk98;
public class BadPath {
StringBuffer path;
public void append (String element) {
if (path == null) {
path = new StringBuffer();
} else {
path.append("/");
}
path.append(element);
}
public String getPath () {
return path.toString();
}
}
We were careful to avoid putting a slash at the start of the path, since we're assuming this object stored relative paths. If we compile this file and try it, it appears to work:
set p [java::new tutorial.tcltk98.BadPath] $p append foo $p append bar $p getPathwhich prints "foo/bar". To test it a little more thoroughly, we write a test suite, javatute/testBadPath.tcl, that calls the test procedure:
test path-1 "Construct a path" {
set p [java::new tutorial.tcltk98.BadPath]
$p getPath
} ""
test path-2 "Single-level path" {
set p [java::new tutorial.tcltk98.BadPath]
$p append foo
$p getPath
} foo
test path-3 "Two-level path" {
set p [java::new tutorial.tcltk98.BadPath]
$p append foo
$p append bar
$p getPath
} foo/bar
test path-4 "Ignore blank elements" {
set p [java::new tutorial.tcltk98.BadPath]
$p append foo
$p append ""
$p getPath
} foo
testDone
When we source this test suite, however, two of the tests fail:
path-1 fails because we get a
java.lang.NullPointerException, and path-4 fails because
we forgot to account for blank elements in the argument to
append.
The corrected code is in the class tutorial.tcltk98.GoodPath. In this version, we have also taken the trouble to properly catch erroneous input in append and throw a new exception that we defined, tutorial.tcltk98.PathException.
package tutorial.tcltk98;
public class GoodPath {
StringBuffer path;
public void append (String element) throws PathException {
if (element.indexOf('/') >= 0 ) {
throw new PathException("Malformed path");
}
if (!element.equals("")) {
if (path == null) {
path = new StringBuffer();
} else {
path.append("/");
}
path.append(element);
}
}
public String getPath () {
if (path == null) {
return "./";
} else {
return path.toString();
}
}
}
In addition to testing for valid return results, we want to test that erroneous input conditions throw the "right" exception. To support this, test.tcl also contains a procedure called testException, that expects a Java exception to be thrown. Here is its declaration:
proc testException {name description script exception {message {}}}
As weell as the test name, description, and script, this procedure is
passed the name of a Java exception class, and, optionally, the string
contained in the thrown exception. The test fails if no exception is
thrown or if the wrong one is thrown.
The new test suite is in javatute/testGoodPath.tcl. It contains six tests, the four from the previous version (modified slightly), and an additional two that check that test for exceptions:
testException path-5 "Throw exception on null" {
set p [java::new tutorial.tcltk98.GoodPath]
$p append [java::null]
} java.lang.NullPointerException
testException path-6 "Throw exception if element has slash" {
set p [java::new tutorial.tcltk98.GoodPath]
$p append "foo/bar"
} tutorial.tcltk98.PathException "Malformed path"
If you source this file, all the tests will pass.
We have shown only the process of running a single test suite. Because we are using Tcl, however, it is easy to construct test procedures for multiple tests, and to produce ad-hoc test runs as needed. For example, to run all test suites in a directory, we might run
foreach f [glob test*.tcl] {
source $f
}
This kind of thing -- gluing together small scripts into larger ones
and producing ad-hoc test runs as needed -- is much, much
easier and faster in Tcl than it would be to write and compile new
Java code each time.