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 testDoneWhen 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.