5.1 Library GUI

This chapter presents a Java Swing implementation of a GUI for the virtual library. It demonstrates test driven development of a GUI application and serves as a more realistic example of a working library application than the previous chapters. However, it is still a demonstration program rather than a practically useful application.

The functional goal of the library GUI is to allow a user to add new Book s to a Library and look up existing Book s. Nothing fancy, but like most GUI applications, there are many elements and behaviors to consider. The application will need a main window, dialogs for adding and finding Book s, and standard GUI functionality such as being able to open and close windows .

Adhering to the TDD mantra, first identify a behavior and write a test for it. The initial target is the Add Book dialog described earlier, which provides a GUI interface for creating a new Book . Example 5-2 shows the unit test for the smart object AddBook .

This test creates an AddBook object, calls its method add( ) , and verifies that the Book has been added to the Library . AddBook 's constructor gives the test the Library to modify; this was an up-front design decision.

The initial implementation of AddBook to pass AddBookTest is shown in Example 5-3.

Since the method addBook() may throw an exception, add() must catch it. Informing the user about the error is something to add to the "to-do" list. Otherwise, the implementation is simple.

The next step is to create the view class, AddBookView . It needs to provide a GUI window, text fields for the title and author, and Add and Cancel buttons . The window title should be "Add Book." The unit test AddBookViewTest verifies all of this, as shown in Example 5-4.

Example 5-5 gives the initial implementation of AddBookView . It is a custom subclass of the Swing GUI class JFrame and contains only the minimum code necessary to pass the test. It completely ignores the layout of the controls.

When Java creates a JFrame -derived dialog window, it does not display it until its show( ) method is called. So, the test AddBookViewTest creates, verifies, and destroys the AddBookView dialog without actually showing it.

Now AddBook can be made to use AddBookView . Some thought must be given to how the smart object and the humble dialog will interact. They will communicate via an implicit internal protocol. The ideal architecture will place all the important behavior in the smart object, and will place all the GUI- related code, such as event handling, in the view. Having both the smart object and the view know about each other is unnecessary. The only necessary interaction between the two is that the view needs to be able to call the methods on the smart object representing its behaviors, so we will follow that model. When the view is constructed , it will get a reference to its smart object.

The most important functionality of this construct is to add a Book when the user clicks on the Add button in the view. This is a GUI-driven behavior, so the unit test belongs in AddBookViewTest . It also implicitly tests that the view invokes the smart object's add() method. Example 5-6 shows this test.

AddBookViewTest is refactored as a test fixture that creates instances of Library , AddBook , and AddBookView in its setUp( ) method. The new test method, testAddButton( ) , sets the title and author text field values, simulates a user click on the Add button using the method JButton.doClick() , and verifies that the Book is added to the Library .

To pass this test, AddBookView requires a number of additions, including a reference to an AddBook and the ability to handle the button click event. Example 5-7 shows the new version of AddBookView . The code to create and add the controls is moved to a new method, addControls( ) (which is not shown, for brevity).

The result of the changes to AddBookView is that it receives notification of user events via the actionPerformed() method. If the event indicates that the Add button was clicked, it calls the method AddBook.add() with the title and author values.

Now the Add Book dialog can be tried out manually. Example 5-8 shows a simple executable class called CreateAddBook that creates the dialog.

Assuming that the directory containing the library classes is in the Java CLASSPATH , CreateAddBook is run as follows :

$ java CreateAddBook

The Add Book dialog appears as just a titlebar. It can be resized to show that the Add button fills the entire frame, as shown in Figure 5-3.

Figure 5-3. The AddBook dialog as it initially appears

The dialog is effectively useless because nothing is being done in AddBookView to arrange and size the controls. Once a better layout is implemented, the dialog looks much better, as shown in Figure 5-4.

Figure 5-4. The AddBook dialog with improved layout

Example 5-9 shows the method addControls() with the new layout code. Aside from arranging the controls with a GridBagLayout , the method sets the dialog to a usable default size with the setSize( ) method.

Why isn't there a unit test for the new layout code? It is overkill to write unit tests for purely visual attributes such as layout positions and control sizes. Not only are such tests tedious to write, but their value is limited. If someone adjusts the position of a control, the code's functional behavior does not change, so why should the unit test fail?

Now a working Add Book dialog is in place, along with unit tests of its functionality. The library GUI needs a lot more to be usable, including a main window, a Find Book dialog, and a lot of related GUI functionality and application logic. Rather than walking through all the steps to build this application, this description skips ahead to the finished result. The final version of the library GUI application contains a number of GUI elements implemented as smart objects with thin view classes, including:

AddBook and AddBookView

FindBookByTitle and FindBookByTitleView

LibraryFrame and LibraryFrameView

The class LibraryFrame is the main application window with an attached menu bar. It acts as the parent for the other windows. Closing it causes the application to exit. It is shown in Figure 5-3Figure 5-3.

Figure 5-5. The LibraryFrame window

Since the view classes have duplicate code and the same interface, it makes sense to create a common base class. The abstract parent class BaseView is a simple subclass of JFrame . Example 5-10 shows the code for BaseView .

BaseView gives the view classes a consistent interface and eliminates code duplication between them. As an abstract class, BaseView should be tested with an AbstractTest. BaseViewTestCase is shown in Example 5-11.

The AbstractTest tests three behaviors that all classes derived from BaseView should exhibit: they are hidden upon creation, become visible after the show( ) method is called, and are hidden again after a WINDOW_CLOSING event is sent.

The unit tests for the view classes derived from BaseView should be subclasses of BaseViewTestCase . Example 5-12 shows AddBookViewTest implemented this way.

Example 5-12. AddBookViewTest implemented as a subclass of BaseViewTestCase

Note how the test implements and uses the factory method getBaseView( ) to create an instance of AddBookView for the tests.

In conclusion, although unit tests for a GUI-driven application use different strategies than tests for ordinary classes, the same basic patterns of unit test development are followed. Each class has a corresponding test class, and each behavior is tested with a separate test method.