JaVa
   

Short Detours

The path we have taken so far in this chapter has a few hidden problems:

The first point, visible variables, is connected to the non-public class properties discussed in , . As mentioned in that section, there are pros and cons about the question as to whether or not "internal things" should be made visible for testing purposes only. In any event, it means that we are coupling the tests to internal things of the implementation, making it more fragile. Although moving the widget creation and the widget referencing to a dedicated class can solve the visibility problem, it often does not lead to the simplest design. The second point is actually a limitation of the mightiness and thus effectiveness of the test cases. For this reason, we will discuss two approaches to solve this point in the following sections.

JFCUnit

JFCUnit is an open source project aimed at creating a framework for unit testing of Swing apps. You can download the latest version from [URL:JFCUnit] and actively contribute to the product's further development. JFCUnit provides support for the following:

The process of creating a JFCUnit test suite does not differ much from coding a normal suite. First of all, we have to derive our test classes from junit.extensions.jfcunit.JFCTestCase. This gives us access to a helper class that supplies methods for finding components and triggering events. Internally, JFCUnit takes care of switching between the test thread and the AWT event thread. To understand this better, we will translate the above test case, testDeleteProduct(), into a JCFUnit test case:

import javax.swing.*;
import junit.extensions.jfcunit.*;
public class CatalogEditorJFCTest extends JFCTestCase {
 public CatalogEditorJFCTest(String name) {...}
 private JFCTestHelper helper;
 private ProductCatalog catalog;
 protected void setUp() {
 helper = new JFCTestHelper();
 catalog = new SimpleProductCatalog();
 catalog.addProduct(new Product("123456"));
 catalog.addProduct(new Product("654321"));
 new CatalogEditor(catalog).show();
 }
 protected void tearDown() {
 helper.cleanUp(this);
 }
 public void testDeleteProduct() {
 CatalogEditor editor =
 (CatalogEditor) helper.getWindow("Product Editor");
 JList productList =
 (JList) helper.findComponent(JList.class, editor, 0);
 AbstractButtonFinder deleteButtonFinder =
 new AbstractButtonFinder("Delete");
 JButton deleteButton = (JButton) helper.findComponent(
 deleteButtonFinder, editor, 0);
 JListMouseEventData listClick =
 new JListMouseEventData(this, productList, 0, 1);
 helper.enterClickAndLeave(listClick);
 MouseEventData deleteClick =
 new MouseEventData(this, deleteButton);
 helper.enterClickAndLeave(deleteClick);
 ListModel listModel = productList.getModel();
 assertEquals(1, listModel.getSize());
 assertEquals(new Product("654321"),
 listModel.getElementAt(0));
 assertEquals(1, catalog.getProducts().size());
 assertTrue(catalog.getProducts().contains(
 new Product("654321")));
 }
}


First, we instantiate JFCTestHelper in setUp(), then we create and display the product editor under test. The method cleanUp(...) in tearDown() removes all remaining windows and dialogs from the desktop. However, the actual events happen in testDeleteProduct(): the starting-point into a JFCUnit test case is normally from the main window of an app (i.e., ProductEditor object in our example). Next, we determine the productList and deleteButton widgets as subcomponents of the editor, and then feed them with the product selection and mouse clicking events. Note that we adopt the actual assert commands virtually as they are from ProductEditorTest of the corresponding test. One important difference to the test approach discussed in the previous subsection is that we search for specific widgets by their types, names, or labels, instead of having to rely on the visibility in the class. Another important difference is that we do not call any methods directly from the widgets, but rather set those events in the event queue that will be triggered from within the GUI in the real world. Note that creating events with all their data is much more complex than a simple method invocation.

The testing approach with JFCUnit has benefits and drawbacks. One major benefit is that the tests are very close to the actual events on a GUI. This means that we can test the behavior in several open windows and dialogs and discover Swing-typical multi-thread errors at the same time. The drawback is that the test cases have to make detours to access the actual components and widgets. Therefore, a JFCUnit test case is too heavy for the role of fine-grained unit test, but it is ideally suited for interaction and integration tests. Note that the current release—0.3 beta—supports only the most important Swing features; unusual things like drag and drop are not yet included.

The AWT Robot

Since JDK 1.3, Java offers a class explicitly conceived for GUI testing: java.awt.Robot. Instances of this class offer ways to simulate mouse and keyboard actions, including the following commands: mouseMove(...), mousePress(...), mouseRelease(...), keyPress(...), and keyRelease(...). However, this interface is on too low an abstraction level for unit tests. For example, mouse movements have to be positioned on exact pixels and each touch of keys has to be determined individually. The Robot class was mainly designed for the development of pure Java solutions in the capture and replay approach. It is also feasible to use an AWT robot to build a test framework similar to the abstraction level of JFCUnit. This relieves the user from routine work like positioning the mouse pointer over a specific component or pressing several keys to enter a character string. Abbot is such a tool.

Other Tools

Abbot [URL:Abbot] is a GUI testing tool built on java.awt.Robot that works on two levels of abstraction:

Another Swing GUI testing tool has been developed by Sun's Netbeans team. Jemmy [URL:Jemmy] is technically similar to JFCUnit since it emulates events being posted to the AWT event queue. [3]Most method calls of the Swing components are not thread-safe, so that they have to be passed explicitly to the AWT thread over SwingUtilities.invokeLater(Runnable) for handling, unless they are invoked from the event-handling code.


JaVa
   
Comments