This guide is for JWalk Toolkit, for integrating JWalk with your own Java applications. The flagship GUI version of the tool, JWalk Tester, is described in a separate User Guide for JWalk Tester.
JWalk provides a toolkit for building your own integrated Java testing applications. All of the JWalk tools were built using the same toolkit of parts.
What could you build with the same toolkit? Here are a few applications that have been built by our colleagues:
an integrated Java editor, compiler and test engine,
a Java-based webservice for testing Java classes,
a plugin for the Eclipse IDE to test Java classes,
an educational tool to automatically grade Java classes submitted by students,
a tool to visualise the high-level states and transitions of Java classes.
All of these applications are built around the core JWalker test engine, which communicates via interfaces using an event-dispatching approach.
You will need the JWalk software, and a JWalk License. Please see the JWalk Download Page and follow the usual instructions for a custom installation.
You will also need the JWalk Documentation, to guide you in building your own project. This was prepared for JWalk 1.1 using the javadoc program. The documentation is a zipped bundle of HTML files in the Java archive JAR format.
Download the JWalk Documentation by using the "download" option, after clicking on the above link. Save the documentation bundle and then unpack it, typically using the Java command:
jar -xf jwalk-1.1-doc.jar
This will install a directory structure under your working directory. Point your web browser to the html/ root folder to load the index file.
The main developer-facing components of the toolkit are all found in the package: org.jwalk and include:
JWalker - the top-level test engine, through which you access the settings and channels and execute tests;
Channels - the communication channels, with which you register listeners to handle event-based communication;
Settings - the global test settings, controlling the strategy, modality, test depth and other parameters;
QuestionListener - an interface receiving QuestionEvents that are directed to the tester as queries;
ReportListener - an interface receiving ReportEvents that are directed to the tester as test results;
Strategy, Modality and Convention - enumerated types for the symbolic test settings;
The following is an example snippet of code that interfaces with the JWalk Toolkit. This would normally be found in a method of your Java application:
import org.jwalk.*; // don't forget
Class<?> testClass = ... // however
JWalker walker = new JWalker();
Channels channels = walker.getChannels();
Settings settings = walker.getSettings();
channels.addQuestionListener(new
MyQuestionListener());
channels.addReportListener(new
MyReportListener());
settings.setTestClass(testClass);
settings.setStrategy(Strategy.PROTOCOL);
settings.setModality(Modality.EXPLORE);
settings.setTestDepth(4);
walker.execute();
This will exercise all interleaved methods of the testClass to a maximum sequence length of 4. Within this code:
MyQuestionListener is a class you provide, that implements the QuestionListener interface;
MyReportListener is a class you provide that implements the ReportListener interface.
These must process QuestionEvent and ReportEvent events, handling all communication with the user, including a means to accept inputs and display outputs. See later for more.
The following is a example snippet of code that interfaces with the JWalk Toolkit and executes the JWalker test engine in a separate thread. This will allow your main application to continue, while the test engine is running:
import org.jwalk.*; // don't forget
Class<?> testClass = ... // however
JWalker walker = new JWalker();
Channels channels = walker.getChannels();
Settings settings = walker.getSettings();
channels.addQuestionListener(new
MyQuestionListener());
channels.addReportListener(new
MyReportListener());
settings.setTestClass(testClass);
settings.setStrategy(Strategy.STATES);
settings.setModality(Modality.EXPLORE);
settings.setTestDepth(2);
Thread thread = new Thread(walker);
thread.setPriority(Thread.NORM_PRIORITY);
thread.start();
This will exercise all high level states and transitions of the testClass to a maximum sequence length of 2.
This will run the test engine in a worker thread with the same priority as the application. Thread.MIN_PRIORITY would run the test engine in the background.
The worker thread handles all exceptions raised during testing, and typically dispatches a Notification for any fault, and an empty silent Notification at the end of the test series. These are handled by MyQuestionListener, your own class.
The following example shows how to upload a custom generator to the JWalker, how to specify where oracle files are saved and also shows how to use the Console for all input and output:
import org.jwalk.*; // don't forget
Class<?> testClass = Vector.class;
Class<?> generator = IndexGenerator.class
JWalker walker = new JWalker();
Channels channels = walker.getChannels();
Settings settings = walker.getSettings();
Console console = new Console();
channels.addQuestionListener(console);
channels.addReportListener(console);
settings.setTestClass(testClass);
settings.addCustomGenerator(generator);
settings.setOracleDirectory(
new File("test/oracles"));
settings.setStrategy(Strategy.ALGEBRA);
settings.setModality(Modality.VALIDATE);
settings.setTestDepth(3);
walker.execute();
This will validate all algebraic constructions of the testClass to a maximum sequence length of 4. Within this code:
Vector and IndexGenerator are two classes that you must supply - see also the custom generator guide;
Console is provided and implements the ReportListener and QuestionListener interfaces
The oracle will be saved to: "test/oracles/Vector.jwk".
The JWalk Editor, built using the parts toolkit
Your own MyReportListener class must publish reports dispatched by the JWalker. It provides one method:
import org.jwalk.out.*; // don't forget
void publish(ReportEvent event) {
Report report = event.getReport();
System.out.println(report.getContent());
}
The above trivial implementation simply prints the content of the Report to standard output. A more sophisticated way of handling reports might be:
import org.jwalk.out.*; // don't forget
void publish(ReportEvent event) {
Report report = event.getReport();
switch(report.getEdition()) {
case Edition.PROTOCOL_REPORT :
handle((ProtocolReport) report); break;
case Edition.ALGEBRA_REPORT :
handle((AlgebraReport) report); break;
...
}
}
This uses the Edition enumerated type to distinguish the kinds of Report, and then typecasts this to a more specific kind, such as a ProtocolReport, or an AlgebraReport.
MyReportListener will supply overloaded handle() methods that do the right thing with each type of report, to suit your application.
MyQuestionListener must publish questions and accept answers from the tester. It asks the question until it receives a suitable response. It provides one method:
import org.jwalk.out.*; // don't forget
Answer respond(QuestionEvent event) {
Question question = event.getQuestion();
System.out.println(question.getContent());
Answer answer = null;
boolean validReply = false;
while (! validReply) {
System.out.println(question.getQuestion());
/* read the response from keyboard */
/* answer = ... convert response */
/* set validReply (true | false) */
}
return answer;
}
The above trivial implementation communicates with the tester by printing the Question on standard output and accepting keyboard input (elided). In this code:
Question-methods: getContent() displays the main content of the query, and getQuestion() displays the reply options. In the above, the reply options will be repeated as a prompt until a valid response is received.
Answer is an enumerated type with the symbolic values YES, NO, OK, QUIT. Different kinds of question will expect a subset of these values.
A more sophisticated way of handling questions might be to determine the kind of question and dispatch to a separate handler for each kind:
import org.jwalk.out.*; // don't forget
Answer respond(QuestionEvent event) {
Question question = event.getQuestion();
switch(question.getEdition()) {
case Edition.CONFIRM_DIALOG :
return handle((Confirmation) question);
case Edition.NOTIFY_DIALOG :
return handle((Notification) question);
...
}
}
This uses the Edition enumerated type to distinguish the kinds of Question, and then typecasts this to a more specific kind, such as a Confirmation or a Notification.
MyQuestionListener will supply overloaded handle() methods that do the right thing with each type of question and return an Answer. Each method may open a different kind of dialog, offering different response options.
Whereas a Confirmation expects one of the answers YES, NO, QUIT, a Notification merely requires an OK response from the tester. A silent Notification (see below) is always sent to indicate the end of a test series.
Notification can indicate the urgency of a notification. This allows an application to launch more appropriate dialogs. The different Urgency enumerated values are:
SILENT - a silent notification, no response needed;
NOTICE - a notice, trigger an information dialog;
WARNING - a warning, trigger a warning dialog;
ERROR - an error, trigger an error dialog.
These can be detected inside the handle(Notification) method of your MyQuestionListener:
import org.jwalk.out.*; // don't forget
Answer handle(Notification notice) {
switch(notice.getUrgency()) {
case Urgency.SILENT :
return Answer.OK;
case Urgency.NOTICE :
// create new Info Dialog
case Urgency.WARNING :
// create new Warning Dialog
case Urgency.ERROR :
// create new Error Dialog
}
}
Each kind of JWalk report has its own API that allows you to extract different information:
ProtocolReport - analysis of the public interface;
AlgebraReport - analysis of the algebraic properties;
StateReport - analysis of the state-space;
CycleReport - test results for one test cycle;
SummaryReport - summary for a whole test series
See the JWalk Documentation for further information.
Applications may need to handle the following five kinds of fatal exception (other exceptions are handled internally):
LoaderException - if the test class, or a custom generator could not be loaded (wrong pathname);
SettingsException - if the application provides wrong symbolic values or out-of-range settings;
PermissionException - if the test class refuses permission to be executed (visibility, security);
GeneratorException - if a type cannot be instantiated, or a user-supplied generator fails
ExecutionException - if a constructor or method could not be called, or behaved randomly (security, nondeterminism)