C. Keith Ray

C. Keith Ray writes about and develops software in multiple platforms and languages, including iOS® and Macintosh®.
Keith's Résumé (pdf)

Monday, March 30, 2015

Unit Testing In Java, Duplicate Code in Java GUIs

(Originally posted 2003.May.28 Wed; links may have expired.)

Johannes Link announces that the book Unit Testing In Java by Link and Frohlich, is now available for purchase, even though Amazon says it isn't yet. Link says the book teaches Test Driven Development in Java, covering GUIs, web-applications, databases, multi-threaded applications and other advanced topics. A sample chapter is available here: [PDF, 28 pages] http://www.bhusa.com/bookscat/samples/1558608680/1558608680.pdf?mscssid=W2B78S2RV83D8K9W260JPX1W0DUJ9DC7.

The sample chapter on test-first Swing GUI programming isn't that different than other writings on Java GUI test-first. Near the end of the chapter they discuss JFCUnit, and briefly mention AWT Robot, neither of which they recommend for typical test-first unit testing. I will order the book and read it.

One nit I'd like to pick: the authors make the JFrame subclass an ActionListener, listening to the "Delete" button and the "Add" button. This is very common in Java books, but that doesn't make it right. Making the JFrame an ActionListener on multiple widgets means we have some tightly coupled pieces of code (reformatted - I like the braces to line up):

public class CatalogEditor extends JFrame
    implements ActionListener, ListSelectionListener 
{
...
    private void addAddButton() 
    {
        addButton = new JButton("Add");
        addButton.addActionListener(this);
        getContentPane().add(addButton);
    }
... etc. (similar code to add "Delete" button)...

    public void actionPerformed( ActionEvent e ) 
    { // blech... nasty.
        if ( e.getSource() == deleteButton ) 
        {
            deleteButtonClicked();
        } 
        else if ( e.getSource() == addButton )
        {
            addButtonClicked();
        }
    }
...

This is nasty because it couples the JFrame too tightly with the buttons it is listening to. If you add another button, you also have to change the actionPerformed method. You would rather not have to change multiple pieces of code when you add "just one more thing". There is a better way (using anonymous subclassing) - don't declare the JFrame to be an ActionListener; instead, pass anonymous implementations of ActionListener to the buttons like so:

public class CatalogEditor extends JFrame
{
...
    private void addAddButton() 
    {
        addButton = new JButton("Add");
        addButton.addActionListener( new ActionListener() 
            {
                public void actionPerformed( ActionEvent e ) 
                {
                    addButtonClicked();
                }
            } 
        );
        getContentPane().add(addButton);
    }
... etc. (similar code to add "Delete" button)...
    // now there is no need for an actionPerformed method that uses 
    // cascading if statements.

I also want to point out the code duplication we have: creating the "Add" button is almost the same code as creating the "Delete" button (not shown). That kind of duplication seems to be common in Java GUI programming, but it is really unforgivable when you have lots of dialogs and buttons. What can we do about it?

First, we could have a DialogCreator class that reads the button names from a string list, creates them, and adds them to the ContentPane of the JFrame subclass (which it could also instantiate). It could also read the layout name and other information needed to lay out the GUI. In order to get the "Add" button's listener to call the method "addButtonClicked" in the JFrame subclass, we need to use the Java reflection API. Here's the pseudo-code:

class ListenerCaller implements ActionListener
{
    public ListenerCaller( Object objectToCall, String methodNameToCall )
    ....

    public void actionPerformed( ActionEvent e )
    {
        ... reflection to call methodNameToCall on objectToCall...
    }
}

Notice that the ListenerCaller class doesn't care what kind of class it is calling. You could connect your GUI buttons to your model classes directly this way. Apple's Cocoa does something similar - the Cocoa widget classes (menus, buttons, etc.) take an object (called "target") and a method selector (easily created from a string) and call whatever object and method that you specify. You would normally do this specification in the Interface Builder.

class DialogCreator
{
    ...
    public void CreateDialog( textfile or string list )
    {
        String jfClassName = read JFrame subclass name from textfile;
        JFrame jfObj = ... reflection to create object given jfClassname;

        String layoutName = read layout name from textfile;
        // also read number of arguments, etc... this can get complicated.
        Layout lay = ... reflection to create object given layoutName, 
             number of arguments, argument types, etc.;

        jfObj.getContentPane().setLayout( lay );

        while not end of buttons
        {
            String buttonName = read button name from textfile;
            String methodToCall = read method to call name from textfile;

            JButton aButton = new JButton( buttonName );
            aButton.addActionListener( new ListenerCaller( jfObj, methodToCall ) );
            jfObj.getContentPane().add( aButton );
        }
    }
}

A single DialogCreator class like this removes button-creation code from all your Swing GUI code. Given that GUIs have a nested hierarchical structure, you probably want your text file to reflect that structure as well, using XML, and creating all kinds of widgets, not just buttons. It turns out that someone has already done all this work (and more) and called it XMLTalk.

I haven't used XMLTalk myself, because I haven't needed to do very much Java GUI programming, and I'm not sure about its license agreement. I haven't downloaded the source, but I have looked at the white papers and other documentation. It is worth studying to see how ValueModel adapters and other helper classes can be used to decouple your GUI code from the model code, and to decouple some of your model classes from each other. I should point out that XMLTalk is based on concepts first(?) implemented in VisualWorks Smalltalk. You should check out their product as well.


UPDATE

(Originally posted 2003.Jun.02 Mon; links may have expired.)

Previously, I used my review of the sample chapter of the book Unit Testing In Java by Link and Frohlich to launch my diatribe against the typical Java-book method of creating widgets in windows/dialogs. Johannes Link send me this comment:

Subject: Unit Testing in Java



Hi Keith,



Compliments on being so quick in reviewing the sample chapter of "Unit

Testing in Java". That's really impressing. 


One thing I'd like to comment on: You're perfectly right when saying that

implementing ActionListener in the window class itself usually leads to loads

of duplicated code. The code in the book, however, is in a temporary state. As

long as there are only two buttons the code is easier to read (IMO) than

when using anonymous inner classes.  Refactoring to a different solution would

certainly take place during one of the next enhancements. The point is: Having

the tests in place allows you to perform this refactoring on stable

ground... 



BTW, your blog really is worth reading.



best wishes,

Johannes Link


When to do refactoring is certainly worth further exploration. Martin Fowler has the "three strikes" rule to remind him to refactor away duplication when a third instance is seen. Extreme Programming recommends "once and only once" to refactor after the second instance is seen. But when your current task is test-driving a dialog or widget-filled window, at what point do you refactor to a dialog-creator class - after the second button, or after the second dialog, or never?

No comments:

Post a Comment