Extreme Java GUI Testing
The idea of Extreme Programming (XP) has been considered by most developers as a way to try to produce quality software in today's fasted-paced development environment. Although not everyone agrees with all aspects of XP, the idea of unit testing is a widely accepted practice. The basic idea is that tests are written as code is being developed. This allows a developer to keep track of the state of the software as it is being developed and also gives them the ability to easily run regression tests as the code is refactored. With today's short development cycles, developing software without unit tests is like driving down a California freeway at rush hour, blindfolded. You're bound to run into an unexpected problem.
For Java developers JUnit is the tool of choice when developing unit tests. It provides a simple and easy-to-use framework for creating and running unit tests. JUnit is a great tool but it doesn't provide the ability to test Swing applications or Web interfaces. Most developers today usually test their user interfaces manually, which is tedious and error-prone. There are a number of commercially available products that can be used to test user interfaces, but these products can be expensive and involve a significant investment in time. They are more geared toward final system testing and are not really meant for unit testing. Two JUnit extensions, JFCUnit and HTTPUnit, are now available that allow users to unit test both Swing applications and Web interfaces with the same ease that the original JUnit is used to test non-GUI code. This article will explain both of these extensions.
A Brief Review of JUnit
The JUnit framework provides a simple set of classes that can be used for unit testing. For more detailed information on Junit, check out www.junit.org. The central class of JUnit is TestCase, which represents a fixture that provides the framework to run unit tests and store the results. TestCase implements the Test interface and provides the methods to set up the test condition, run tests, collect the results, and tear down the tests once they are complete The TestResult class is used to collect the results of a TestCase. The TestSuite class is used to collect a set of tests to run. JUnit also provides a command-line class (junit.textui.TestRunner) and a GUI class (junit.ui.TestRunner) that can be used to run unit tests and display results.
StringTester is a subclass of TestCase. The constructor has one argument, name. This name will be the name of the unit test being run and will be used in any messages displayed by JUnit. The next method in the listing is
setUp() and is used to initialize a couple of strings. The next three methods (
testConcat()) are the actual tests. The test methods must be no argument methods that test a specific condition using one of JUnits assert methods. If the conditions of the assert are true, then JUnit marks the test as passed, otherwise the test is marked as failed. That's all there is to creating a unit test with JUnit. Once a developer is familiar with Junit, creating a unit test is quick and easy.
Testing a Swing application is a lot different than testing other Java classes. To test a Swing application, you would want to start the application and then interact with it, typing in some text and maybe pressing some buttons. If you tried to test a Swing application with the normal JUnit classes, you would run into two problems. The first problem would be that your tests would start running before the Swing application is started, because JUnit is run in a separate thread from the application. So what would happen is that you would start the Swing application in the constructor of the JUnit test and then start running the test methods. Since the Swing application would probably take some time to start, the test methods would fail.
The second problem is that even if test methods waited for the Swing application to start, they would have a hard time interacting with the Swing application, because there is no easy way to find and manipulate Swing components from outside the application. Java does have the Robot class, but it is very low-level and limited. It provides a way to automatically enter keystrokes and mouse events. If you wanted to use the Robot class to test Swing applications, you would have to create an additional API that could be used to find the location of the components that you want to test.
The JFCUnit extension solves these two problems. The first thing it provides is an extension to JUnit's TestCase that allows the unit test to better work with the AWT thread of the Swing application. The next thing that JFCUnit provides is the JFCTestHelper class. This class is started at the beginning of the unit test and listens to the events on the AWT thread. It keeps track of all the components that are created and has finder methods that can be used by the test methods to get a handle to any component.
The last thing that JFCUnit provides is a way to interact with the AWT thread. During test method execution, the AWT thread is blocked in order to interact with the Swing application. JFCUnit provides two methods (
awtSleep(long time) ) that can be used to wake up the AWT thread and allow it process events.
Lets consider a simple Swing application and show how it can be tested using JFCUnit. The figure below shows the Swing application to be tested. It contains three tabbed panes. In the Form pane, there are two text fields and two buttons. When the Copy Text button is pressed, the text in the top field is copied to the bottom field, and when the Clear button is pressed, the text in the bottom field is erased.
If you were to test this Swing application, what you would do is to start the application, type some text into the top field, then press the Copy Text button and make sure the text in the two fields matched. After that, you would press the clear button and make sure the text in the bottom text field was erased.
All these actions can be performed using JFCUnit. The full set of source code for all the applications and units tests shown in this article can be downloaded at here.
The constructor of the JFCUnit test is slightly different than a normal JUnit test. Besides calling its parent constructor, it also creates an instance of the JFCTestHelper class to listen to the AWT thread and keep track of all the Swing components. The construction is also the place where the Swing application is started. It is important to understand that in a JUnit test the constructor gets called before each test method is called, so some logic had to be added to the constructor to make sure the Swing applications is only started once.
Once the testCopyFormView method has the handles to the TabbedPane, it uses the findNamedComponent method to get a handle to the text fields and copy button. Test method
testCopyFormView is used to test the Form tab of the Swing application. The first thing it does is call
awtSleep to make sure all the AWT events have been processed. The next thing it does is get the handles to the main frame and the TabbedPane of the application. The
GetFrames() method uses the
findComponent methods of the JFCTestHelper class to find the main frame and TabbedPane. The way the findComponent works is that it looks for an instance of the specified class in the given window and returns the first instance. If there were more than one TabbedPane, then some logic would have be added to go through all the instances available to find the correct one.
testCopyFormView method has the handles the TabbedPane it used the findNamedComponent to get a handle to the text fields and copy button. The findNamedComponent is a little easier to use, because it looks for a component of a specific name in the frame specified. If it is possible to modify the Swing application so that each component uses the
setName method to create an unique name for each component, then the findNameComponent is the best finder to use, because it will return an unambiguous result.
Now that the testCopyFormView method has all the components, the test concludes by setting the top text field, pressing the Copy Text button, and then checking the results using an
The methods for testing the other tabs of the application are included in the downloadable source code. These methods show how to validate values in a table and make sure the contents of a list are correct when a certain tree node is selected. Although the example presented is a simple one, it does show all the steps needed to use JFCUnit to test Swing applications.