TESS is the (Te)st (S)ystem for (S)-Lang, which aims at reducing the workload and ad-hoc nature of regression testing S-Lang software ( http://www.jedsoft.org/slang/), by collecting common testing elements into a single, easy-to-use framework. TESS provides the S-Lang developer nominal mechanisms for tailoring the S-Lang environment and invoking functions with arbitrary inputs, while transparently inspecting and cleaning the stack, gathering pass/fail statistics, and providing error recovery from exceptions.
TESS is primarily a development tool, so we assume the reader is
familiar with scripting in S-Lang. Knowledge of how to create S-Lang
modules is also helpful. Since TESS is intended to assist in automating
and simplifying regression testing, it is most often utilized in
conjunction with the make tool via a Makefile. If these are
unfamiliar terms then you will benefit from learning about them first
prior to reading further.
Please note that as of version 0.3.0 TESS no longer supports S-Lang 1. This enables TESS to be implemented as purely interpreted code (i.e. S-Lang and shell scripts, no binaries), and makes it smaller, easier to build & use, as well as easier to bundle in other packages (only 2 small text files are required).
Suppose you had a function defined within some file add.sl as
define add()
{
variable i, j;
if (_NARGS < 2) usage("add(i,j)");
(i,j) = ();
return i+j;
}
and that you wanted to exercise it in automated fashion on a range of
input conditions. To test it with insufficient arguments, for instance,
one might write a script add.t
() = evalfile("./add");
add(1);
which, when invoked from slsh as
slsh ./add.t
yields something like
Usage: add(i,j)
called from line 2, file: ./add.t
Ok, so far things look reasonable. Now, suppose you appended the line
add(1,"two");
to the test and reran it in slsh. Curiously enough, the output generated
would look no different from above. Why? Because the first add()
call generates a usage exception, which causes the interpreter to unwind
the S-Lang stack and then exit. In other words, the second add()
call is never even executed! One way to address this is to modify
the script by adding an ERROR_BLOCK, such as:
() = evalfile("./add");
define test1()
{
ERROR_BLOCK { _clear_error(); return; }
add(1);
}
test1;
add(1, "two");
Now both tests will be exercised when the script is invoked, generating
a usage exception in the first case and a type mismatch error in the
second. Progress, for sure, but the script has grown longer, and we
needed to introduce a wrapper for the first test case in order to
use the error block. In this model, where one script contains multiple
test cases, each test would need to be invoked within such a wrapper,
which explains why TESS offers the tess_invoke() function.
An alternative to the model used above would be to each test case within
its own .t file and invoke slsh upon each. This approach avoids
the need for error blocks, but introduces a number of other concerns
which collectively steer the author towards a preference for the first
model.
For example, the resultant file proliferation makes it more cumbersome to
enumerate/name and effectively organize the test suite. Packages of only
moderate size might conceivably contain scores or perhaps hundreds of
relatively tiny files, needlessly wasting disk resources, increasing sizes
of software distributions, and wasting CPU cycles by launching slsh
once per test instead of only once per group of tests. Moreover,
each test invocation would also need to load both TESS and the package
being tested, resulting in yet more CPU waste and code duplication.
Identifying semantically related test cases would not be as easy, since groupings are now distinguished by like-named files within a directory, instead of by cohabitation of test cases within a single file.
Collecting useful failure statistics becomes more difficult, since in this
model aggregate counts can be be obtained only by metascripts, e.g. invoked
within Makefiles at the operating system level to keep track of the 1 or 0
returned from each test, instead of within the test scripts themselves.
In contrast, the tess_invoke function mentioned above transparently
tallies pass/fail statistics.
For small test suites these issues may be negligible, but as packages grow their cumulative effect may not be so easily ignored, making it better to cultivate the preferable habit of "starting clean," rather than one of "cleaning up later".
TESS emerged as the coalescence of scripts used in testing a number of existing S-Lang packages. In fact, development versions of packages such as SLgtk and SLxpa have already been migrated from their original testing scheme towards TESS, and as such they serve as the wealthiest source of supplemental examples to this documentation.
By distilling common patterns from existing test suites into a self-contained distribution TESS fosters reuse, reduces duplicative busy work, and hopefully serves to ease the burden of testing (perhaps one of the least loved aspects of writing and maintaining software).