A Quick Tutorial on JET

Yoonsik Cheon
Department of Computer Science
The University of Texas at El Paso
500 W. University Ave
El Paso, TX 79968-0518
ycheon@utep.edu

Abstract

JET is an automated unit testing tool for Java classes annotated with JML specifications; JML is a formal interface specification language for Java to document the behavior of Java classes and interfaces. JET tests each method of the class under test separately. For each method, it generates a collection of test data, executes them, and decides test results (i.e., pass/fail) by using JML specifications as test oracles, thereby fully automating unit testing of Java classes. This document gives a quick tutorial introduction to JET.

1  Introduction

JET [3] is an automated unit testing tool for Java classes annotated with JML specifications (refer to Section 2 for JML). It tests each method of the class under test separately. Testing with JET is fully automated in the sense that each step of the testing is performed automatically, including test data selection, test execution, and test result determination. Specifically, the following steps are performed automatically in order to test a method.
  1. Generate a test case. A test case for a method generally consists of a receiver object and arguments. For example, to test a method void transfer(int, Account) of class Account, JET generates two account objects-one for the receiver and the other for the argument-and selects one integer value. It generates or selects test data randomly. For a class such as Account, a random object is created by making a series of method invocations preceded by a constructor invocation [5].
  2. Execute the test case. The method under test is invoked with the generated test case. The runtime assertion checks are enabled. That is, JML specifications for the method, such as method pre and postconditions, are checked at runtime during the test execution.
  3. Decide the test result. The test result (i.e., test pass or fail) is determined by monitoring occurrences of JML assertion violations. In general, a precondition violation means that the test case is inappropriate to test the method, and a postcondition violation means that the test failed, i.e., the implementation doesn't satisfy the specification for that test case [4].
JET also provides other features. For example, the generated test cases can be selectively exported as JUnit test classes for regression testing, as well as for incorporating manually created test data. JUnit is a popular unit testing framework for Java to organize a large collection of test data [1]. In addition, JET can be used as a simple integrated development environment (IDE) for Java and JML. Using JET, you can edit, check, compile, and run Java/JML programs.
JET has known limitations. The primary limitation is that the current implementation doesn't support Java 1.5 features such as generics. This is because JML itself doesn't support these features yet.
The JET tool is available for free from the JET website. Refer to Appendix A.2 for instructions on downloading and installing JET.
The remainder of this document is structured as follows. In Section 2 we briefly introduce JML through an illustrative example that will be used throughout this document. In Section 3 we explain how to launch JET tool, and in Sections 4 and 5, respectively, we explain the use of JET as an IDE for Java/JML and as an automated unit testing tool for Java classes. The appendix at the end gives the complete JML specification of the running example and instructions on downloading JET.

2  The JML Language

JET uses JML as a test oracle specification language in that the result of a test execution is determined based on occurrences of runtime violations of JML assertions. JML, which stands for the Java Modeling Language, is an interface specification language for Java to formally specify the behavior of Java classes and interfaces [6]. In JML, the behavior of a Java class is specified by writing class invariants and pre and postconditions for the methods exported by the class. The pre and postconditions are viewed as a contract between the client and the implementor of the class. The client must call a method in a state where the method's precondition holds, and the implementer must guarantee that the method's postcondition holds after such a call. The assertions in class invariants and method pre and postconditions are usually written in a form that can be compiled, so that violations of the contract can be detected at runtime.
public class Account {
  private /*@ spec_public @*/ int bal;
  //@ public invariant bal >= 0;

  /*@ requires amt >= 0;
    @ assignable bal;
    @ ensures bal == amt; @*/
  public Account(int amt) {
    bal = amt;
  }

  /*@ requires amt > 0 && amt <= acc.bal;
    @ assignable bal, acc.bal;
    @ ensures bal == \old(bal) + amt
    @   && acc.bal == \old(acc.bal - amt); @*/
  public void transfer(int amt, Account acc) {
    acc.withdraw(amt);
    deposit(amt);
  }

  // The rest of the definition ...
}

Figure 1: Example JML specification
Figure 1 shows an example JML specification. JML assertions are written as special annotation comments in Java source code, either after !//@! or between !/*@! and !@*/!. The keyword !spec_public! in line 2 states that the private field !bal! is treated as public for specification purpose; e.g., it can be used in the specifications of public methods. The next line specifies a class invariant stating that the value of bal is always non-negative. As shown in lines 5-7, a method (or constructor) specification precedes the declaration of the method (or constructor). The !requires! clause specifies the precondition, the !assignable! clause specifies the frame condition (that states which state variables may be changed), and the !ensures! clause specifies the postcondition. The keyword !! in line 14 denotes the pre-state value of its expression, i.e., the expression is evaluated just before the method invocation; it is most commonly used in the specification of a mutation method such as !transfer! that changes the state of an object. The complete specification of the Account class is found in Appendix A.1.
For more information about JML including tools [2], tutorials, example specifications, reference manual, technical papers, and other documents, refer to the JML website at http://www.jmlspecs.org.

3  Running JET

JET can be freely downloaded from the JET website (see Appendix A.2). The binary distribution of JET includes an executable JAR file, most likely named jet.jar. To launch the JET tool, run the executable JAR file. To run an executable JAR file, you can use the -jar option of the java command; e.g., type the following line on the command prompt.
  java -jar jet.jar

Alternatively, you can double-click the executable JAR file on an operating system like Microsoft Windows; however, this works only if the Java Runtime Environment (JRE) is installed and JAR files are associated with the JVM.

4  Developing Java Programs with JET

You can use JET as a simple integrated development (IDE) for Java/JML. In this section we will develop a Java "hello world" program using JET. In particular, we will create a Java program file, edit the source code, compile it, locate and fix compilation errors, and run the compiled bytecode file.

4.1  Creating Java Programs

We will first write our small program by creating an empty file, editing it, and saving it to a file named HelloWorld.java. You can create a new Java file or open an existing file by using either the File menu or the directory browser. The directory browser is located at the left side of the GUI and allows one to browse directories and Java files (see Figure 2). Here we will use the directory browser to create an empty file and open it for editing.
fig-creating-file.gif
Figure 2: Creating a new file
Let us first create a new empty file named HelloWorld.java. For this, first select the parent directory-the directory in which to create the file-by expanding all its ancestor directories starting from the root directory.1 With the parent directory selected, click the right button of the mouse to bring up the pop-up menu (see Figure 2). From the pop-up menu, select the New menu item. The system will show a dialog window asking the name of the file to be created. Enter HelloWorld.java and click the OK button. The file will be created and listed under the selected parent directory.
Let us next edit the newly created file. For this, select the file and choose Open from the pop-up menu or drag and drop the file to the main pane of the GUI where the JET logo is displayed. The file will be opened for editing and its (empty) contents will be displayed in the built-in Java/JML editor. Type in the following code in the editor.
  public class HelloWorld {
   public static void main(String[] args) {
    System.out.println("Hello, world!");
   }
  }

Once typing is done, let us save the file. For this, use either the Save menu item of the File menu or the corresponding tool bar icon button.
fig-compiling-file.gif
Figure 3: Compiling Java programs

4.2  Compiling Java Programs

Let us now compile our little program. For this, select the Compile Java menu item from the Tool menu (see Figure 3); you can alternatively click the corresponding tool bar icon button. This will make JET to compile the current file being edited, i.e., HelloWorld.java. If there is no compilation error, a bytecode file named HelloWorld.class will be created and listed in the directory browser (see Figure 3). Otherwise, compilation error messages will be displayed in the output pane that will appear at the bottom. The source of a compilation error can be easily located by clicking the error message line that displays the file name and line number in the output pane. The editor will highlight the corresponding line of the source code file.
Note that JET uses a default Java compiler to compile Java programs, and depending on your environment you may need to change the default compiler for the compilation to work. The default compiler can be changed with the Option menu (see Section 4.4).

4.3  Running Java Programs

You can run the compiled bytecode file by either selecting the Run menu item from the Tool menu or clicking the corresponding tool bar icon button. The output of the program, if exists, will be displayed in the output pane at the bottom; the output pane will appear automatically if it's not already shown yet. In our case, a Hello, world! message will be displayed.
As in compilation, JET uses a default JVM to run Java programs. Depending on your system settings, you may need to change the default JVM to be able to run a Java program. The default JVM can be changed with the Option menu (see Section 4.4).

4.4  Configuring JET for Compilation

When you run the JET tool for the first time, you may need to tell JET which Java compiler and JVM you want to use for compiling and running Java programs, respectively.2 By default, JET uses the commands javac and java for Java compiler and JVM, respectively. If these commands are not available on your system or if you want to use a different compiler or JVM, you need to change the default values.
fig-comp-conf.gif
Figure 4: Configuring JET for compilation
To change the default Java compiler or JVM, select the Options menu item from the Options menu (see Figure 4). A dialog window will appear that allows you to change various JET options, including compilation options. Select the Compilation tab from the list of tabs at the top. In the tabbed pane, you can now set the Java compiler or JVM by specifying a command or executable file. You can also set other compilation options such as CLASSPATH.

5  Testing Java Classes with JET

In this section we will learn how to perform a fully automated unit test for a Java class annotated with JML specifications. In general, you first compile the class with the built-in JML compiler and then load the compiled bytecode file. JET will display all the public methods including constructors of the class. You select a set of methods to test, and JET will test one method at a time from the selected set of methods by generating and executing test data and showing the test results.

5.1  Compiling a Java Class for Testing

Let us test the Account class shown in Section 2. The complete definition of the class is found in Appendix A.1. The first step of automatic testing is to compile the class with the built-in JML compiler. For this, let us either create the Account class (see Section 4.1) or download it from the JET website (see Appendix A.2). If you download it from the website, open the file with JET; we will not actually edit it but we need to open it first to compile it. For this, use the Open menu item of the File menu; alternative you can drag the file from the file browser and drop it to the editor (or the main pane if the editor is not active).
Compiling a class with the built-in JML compiler is similar to compiling it with a Java compiler (see Section 4.2). To compile the Account class, select the Compile JML menu item from the Tool menu or click the corresponding tool bar icon button. If there is no compilation error, a bytecode file named Account.class will be created and displayed in the directory browser. If there is any compilation errors, error messages will be displayed in the output pane, and their sources can be located by clicking the error message lines that display the file names and line numbers (see Section 4.2).

5.2  Loading a Bytecode File for Testing

The next step is to load the compiled bytecode file to JET. To load a bytecode file for automated testing, you can either use the pop-up menu of the directory browser or drag and drop the file directly to the editor (or the main pane if the editor is not active). Let us load the Account class. For this, select the bytecode file Account.class from the directory browser and press the right button of the mouse to bring up the pop-up menu (see Figure 5). From the pop-up menu, select the Test menu item. The system will load the selected class and show all the public methods of the class that can be tested in the Methods pane (see Figure 5). We will test some of these methods in the next subsection.
fig-loading-class.gif
Figure 5: Loading a class

5.3  Testing Methods

We are now ready to test the methods of the Account class. It is simple to test the loaded class. You only need to select a set of methods to test and initiate an automated test by clicking some menu item of the pop-up menu of the methods pane. Let us actually test a method of our account class, say the transfer method. First, select the method from the methods pane and press the right button of the mouse to bring up the pop-up menu3 (see Figure 6). Then, select the Test selected menu item.
fig-testing-class.gif
Figure 6: Testing a class
The system starts testing the selected method by listing generated test data along with their test results one at a time (see Figure 74). It also shows in a dialog window test statistics, e.g., the numbers of meaningless (or invalid) test cases, successful test cases, and failed test cases. The generated test cases are shown in the Test Cases pane. A check mark in the listing indicates a successful test case; all tests succeeded.5 If you click a particular test case, the system will display the details of the test case in the Description pane. For example, the description pane of Figure 7 shows the details of the first test case, consisting of a receiver of type Account and two arguments, an int value and an Account object, respectively; remember that we are testing the transfer method that transfers a given amount of money from one account to another.
fig-testcase.gif
Figure 7: Test results
Our first automated test with JET was not that productive, as we didn't find any error. Let us inject a fault to the Account class to learn how JET detects and reports a test failure. Edit the Account.java file to change the definition of the transfer method as follows.
public void transfer(int amt, Account acc) {
 acc.withdraw(amt);
 // orig. was: deposit(amt);
 deposit(amt + 10); 
}

For this editing, you may first need to enable the editor using the View menu. If necessary, also enable the directory browser to drag and drop the source code file to the editor. Once edited, save the file and compile it with the JML compiler (see Section 5.1). Finally, load the compiled bytecode file again (see Section 5.2).
Now, retest the transfer method. You will see at this time that all generated test cases fail (see Figure 8). If you click a failed test case, the system will display the reason of failure, as well as the description of the test data. For example, the first test case of Figure 8 failed because it violated the assertion on line 25 of Account.java. If you click the error message giving the file name and line number of the violated assertion, the system will locate the violated assertion by highlighting the corresponding source code line in the editor, if necessary, after opening the file first (see Figure 8).
fig-test-failure.gif
Figure 8: Locating the source of a test failure

5.4  Exporting Tests as JUnit Classes

You can export generated test cases as JUnit test classes. This is handy not only for regression testing but also for integrating automated testing with manual testing. Let us export some of failed test cases from Section 5.3. First, select several test cases from the Test Cases pane (see Figure 9); to select multiple test cases, press both the control (or shift) key and the left mouse button at the same time. Then, press the right mouse button to bring up the pop-up menu. From the pop-up menu, select the Export selected menu item. As shown in the figure, the system will ask for the file name of the JUnit test class; use the default (i.e., AccountTest.java). All the selected test cases will be exported to the named test class as JUnit test methods, one test method per test case. You can compile and run the exported test class like any Java programs as you do with other JUnit test classes.
As an exercise, open the exported test class with JET. Can you see how a test case is translated to a JUnit test method? Compile and run the test class with JET. Does it give the same test failures?
As another exercise, first fix the fault that we introduced in Section 5.3 (by recompiling it with the JML compiler) and then run the exported test class again. Do all test cases succeed at this time? Can you see how you can use the exported test class for regression testing?
fig-export-test.gif
Figure 9: Exporting tests

5.5  Abstract Classes and Interfaces

JET generates test data randomly. For a class type such as Account, it generates a random object as a sequence of method invocations preceded by a constructor invocation; examine some of the test data generated for the transfer method in Section 5.3. How does JET generate random objects for abstract classes or interfaces? It simply can't; the only value it can generate is null. However, if you specify concrete implementation classes for abstract classes or interfaces, JET will create random objects of the concrete classes and use them when instances of the abstract classes or interfaces are required.
You can specify concrete implementation classes for abstract classes or interfaces by using the Options menu. Click the Tester tab (see Figure 10). As shown in Figure 10, for each abstract class or interface you can specify a list of concrete classes separated by a comma. If, as in the example, more than one concrete class are specified for the same abstract class or interface, the system picks a class randomly; e.g., either ArrayList or HashSet will be used for the Collection interface.
Besides specifying concrete implementation classes, there are several other options that you can set, such as checking redundancy of generated test data, reuse of generated objects through object pooling, and the maximum number of attempts for generating valid test data. However, some of these options are likely to be changed as JET continues to evolve.
fig-test-options.gif
Figure 10: Setting test options

5.6  Incorporating Manually-written Test Data

Automated testing is fast and cost-effective. However, it may not be able to find or generate specific or particular test data that a tester has in mind. JET allows you to complement automatically-generated test data with manually-written test data. Basically, you extend JET-exported test classes with manually-written JUnit test methods.
Let us add a manually-written test method to the AccountTest class that we exported in Section 5.4. For the ease of maintenance, instead of directly modifying the exported test class, let us introduce a new class, say ImprovedAccountTest, as a subclass of the exported class. Let us assume that you want to include a new test case for transferring the whole balance of the argument account. Then, all you need to do is to add a new JUnit test method, something like the following, to ImprovedAccountTest.
  public void testTransferImproved() {
   Account r = new Account(100);
   Account a = new Account(200);
   r.transfer(200, a);
   assertEquals(300, r.balance()); 
   assertEquals(0, a.balance());
  }

Alternatively, rather than writing your own test oracles in terms of JUnit's assert methods as shown above, you can use JML specifications as test oracles. For this, construct test data and simply call the test oracle method oracleTransfer by supplying the constructed test data; the test oracle method is generated and exported as a part of the exported JUnit test class (i.e., AccountTest).
  public void testTransferImproved() {
   Account r = new Account(100);
   Account a = new Account(200);
   oracleTransfer(r, 200, a);
  }

As an exercise, add boiler-plate JUnit methods such as suite to the ImprovedAccountTest class; for a hint, look at the exported class AccountTest. Use JET to edit, compile, and run it.

6  Conclusion

In this tutorial we explained the basic use of JET both as a fully-automated unit testing tool for Java classes and as a lightweight IDE for Java/JML. As a research product, JET will continue to evolve, e.g., by incorporating different techniques for generating test data. We welcome comments, suggestions, or feedback on the JET tool itself, as well as this tutorial.

Acknowledgment

The development of JET was supported in part by the National Science Foundation under Grant No. CNS-0509299 and by a contract from the US Army Space and Missile Defense Command (SMDC) and the Homeland Protection Institute (HPI) to the Center for Defense Systems Research (CDSR) of the University of Texas at EL Paso (UTEP).

A  Appendix

A.1  JML Specification of Class Account

The complete definition of class Account including JML specifications is given below; it is also available online at the JET website (see Appendix A.2).

A.2  Downloading JET

The JET tool is written in Java and freely available to download from its website at http://www.cs.utep.edu/cheon/download/jet.html. Besides an executable JAR file, you will also find source code, API specifications, and other documents from the website.

References

[1]
K. Beck and E. Gamma. Test infected: Programmers love writing tests. Java Report, 3(7):37-50, 1998.
[2]
L. Burdy, Y. Cheon, D. R. Cok, M. D. Ernst, J. R. Kiniry, G. T. Leavens, K. R. M. Leino, and E. Poll. An overview of JML tools and applications. International Journal on Software Tools for Technology Transfer, 7(3):212-232, June 2005.
[3]
Y. Cheon. Automated random testing to detect specification-code inconsistencies. Technical Report 07-07, Department of Computer Science, The University of Texas at El Paso, 500 West University Ave., El Paso, TX, 79968, Feb. 2007. To appear in SETP 2007, Orlando FL, July 9-12, 2007.
[4]
Y. Cheon and G. T. Leavens. A simple and practical approach to unit testing: The JML and JUnit way. In B. Magnusson, editor, ECOOP 2002 - Object-Oriented Programming, 16th European Conference, Máalaga, Spain, Proceedings, volume 2374 of Lecture Notes in Computer Science, pages 231-255, Berlin, June 2002. Springer-Verlag.
[5]
Y. Cheon and C. E. Rubio-Medrano. Random test data generation for Java classes annotated with JML specification. Technical Report 07-11, Department of Computer Science, The University of Texas at El Paso, 500 West University Ave., El Paso, TX, 79968, Mar. 2007.
[6]
G. T. Leavens, A. L. Baker, and C. Ruby. Preliminary design of JML: A behavioral interface specification language for Java. ACM SIGSOFT Software Engineering Notes, 31(3):1-38, Mar. 2006.

Footnotes:

1The root directory of the directory browser can be changed from the pop-up menu or the Option menu.
2The JVM is also used to run the built-in JML compiler for automated testing (see Section 5) . The JML compiler is a Java application.
3You can also test multiple methods at the same time by selecting multiple methods. To select multiple methods, press both the control (or shift) key and the left mouse button at the same time.
4The figure shows only the tester pane. By using the View menu, you can hide all other panes such as the directory browser, the editor, and the output pane.
5This is also indicated by the green colored bullet in the methods pane.


File translated from TEX by TTH, version 3.77.
On 13 Jun 2007, 15:53.