The Source for Java Technology Collaboration
User: Password:



Tom Ball

Tom Ball's Blog

'Tis a Gift to be Simple

Posted by tball on November 20, 2004 at 09:49 PM | Comments (7)

'Tis a gift to be simple, 'tis a gift to be free,
'Tis a gift to come down where we ought to be,
...
To turn, turn will be our delight
'Till by turning, turning we come round right.

-- Old Shaker hymn

It was supposed to be a quick-and-dirty mini-project: add a visual unit test execution monitor to NetBeans, similar to the top part of my favorite test tool, JUnit's Swing-based TestRunner:

JUnitTestRunner.gif


After enhancing Ant's JUnit task to log JUnit's TestListener events as messages (hopefully the Ant team will accept these changes soon), the rest seemed easy: listen for Ant build events and display the result of these new JUnit event messages. But things started to get messy when I tried to create a single class that handled the build listening and the display, as there are threading issues with Ant running in one thread and the AWT event dispatch thread in another. To make matters worse the class was hard to test, which is especially embarrassing in a test tool!

I loathe writing GUI tests, so looked to the MVC pattern to move as much code as possible into non-GUI classes which can be tested from the command-line. I found yesterday that this is a recommended strategy by the Test-Driven-Development crowd in this article, Agile User Interface Development by Paul Hamill. I wasn't avoiding unpleasant work, I was just following TDD guidelines (even though I didn't know it then!).

First, the GUI panel was moved to a new View class. Initially I kept the current state for each form element in the View, but since the goal was to make the utility more testable, that state was moved into a simple Model class which fires PropertyChangeEvents. Although PropertyChangeEvents have a reputation for being heavyweight and slow, they make sense here because few events are fired (one event per test suite, one per test, and one per error or failure). The Model's unit test was written quickly and proved important, as it found three bugs in the space of ten minutes typing. The thin View was now isolated from the other classes, as it could reference the model when needed via PropertyChangeEvent.getSource().

So my monitor now had a Model and a View, but where was its Controller? Swing documentation has always been pretty vague about its Controller definition, but I understand a Controller to be the code that translates events into model updates. A button controller, for example, takes a mouse click or an "Enter" keyboard event and converts it into a button model "fire". With that definition, my "build listener" is really a Controller that filters events from Ant's BuildListener interface and updates my Model when it finds a change. For example, my Controller translates an "endTest" TestListener event into an "incrementTestsRun" method invocation on the Model. Things are getting simpler.

With that perspective, it proved easy to separate out the actual Controller code from the application logic, which creates and wires together the Model, View, and Controller instances. Testing outside of the IDE became much easier as well (this is a nice side-effect of design improvement), as a standalone build listener app is now just a few lines of code. I think the secret to finding Controller patterns in existing Swing applications is to remember that controllers "listen" to event streams and update models based on those events. Not all EventListener implementations are true controllers, of course, but when looking to factor out controller logic into its own class, look to the event listeners as they tend to hold much of that logic.

Ensuring thread-safety proved trivial after separating the model, view and controller, as is much easier now to comprehend each thread's control flow. Upon receiving an Ant build event, the controller updates the model, which fires a PropertyChangeEvent. The view's propertyChange method invokes SwingUtilities.updateLater() with a method that updates the form values and then calls its revalidate method. There's no back-and-forth between the model, view and controller: the controller updates the model, the model notifies the view, and (on the event dispatch thread) the view reads the model to redraw itself.

Some engineers pride themselves on writing complex, hard-to-understand code, and sneer at simple code as being "obvious". I believe that writing simple code requires extra work and discipline, but that work is justified by the code's quality, ease of testing and comprehension by my co-workers. When a design gets refactored to the point where it is "obvious", it means that it's time to move to other areas which are not so clear.

The proof that this refactoring exercise was worth the effort came this week when I had the present this JUnit enhancement code to another engineer for the first time, yet the code required little discussion before he fully understood and accepted it. All of this "turning, turning" had "come round right"; even though the Shakers were gone long before programming began, their focus on simplicity remains still important to developers today.

Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • Your MVC approach is nice and very appropriate. However, I would move the SwingUtilities.invokeLater into the controller class. When writing a new view component for the model I shouldn't have to worry about model events not being received on the EDT. Going along with the general Swing single threading rule, when a component is called that call should be happening on the EDT. Therefore, moving from the Ant thread onto the EDT should happend directly in the controller.

    Posted by: fworsley on November 22, 2004 at 01:10 PM

  • Thanks for your comment. I have to disagree with your suggestion, however, as currently both the Model and Controller classes can be tested without a GUI, since the View is the only class that has any references to Swing. The JavaBeans spec. has no knowledge of Swing or the event dispatch thread; since the View implements the JavaBeans PropertyChangeListener interface, it should therefore hide its thread dependencies. A bonus of keeping all Swing references in the View is that non-Swing Views (like an XML report generator) could be used without changing the other classes.

    Posted by: tball on November 22, 2004 at 01:53 PM

  • Thanks for your reply, that is a good point. Maybe in this case a Swing-specific model subclass that uses invokeLater when it fires the event? I'm not sure, it just seems wrong to have to remember to use invokeLater in the view itself.

    Posted by: fworsley on November 22, 2004 at 02:01 PM

  • Do you have a link to the code (for us lazy people)?

    Posted by: dog on November 22, 2004 at 09:06 PM

  • Thanks for sharing your thoughts about MVC with us.

    Regarding the EDT - IMHO both fworsley and you are right: Moving events to the EDT is neither in the responsibility of the view nor of your controller.

    There's missing something between them, with Spin it could be added with a change to a single line:

    model.addPropertyChangeListener(Spin.over(view));

    Posted by: svenmeier on November 23, 2004 at 12:27 AM

  • The code isn't available yet because I haven't done the "due diligence" process for releasing public code yet on netbeans.org. It would be done by now, except that the NetBeans junit module team want to teither ake and maintain this code, or replace it with something similar. Maybe I can post a MagicDraw UML description instead.

    The Spin modification is really interesting, thanks for suggesting it. For some reason I thought Spin only worked with interfaces, but if it's that easy I need to cut over to it!

    Posted by: tball on November 23, 2004 at 01:40 PM

  • You're right, Spin works with interfaces only.
    Fortunately all listeners (that I know of) are based on interfaces: in your case PropertyChangeListener.

    Posted by: svenmeier on December 22, 2004 at 08:39 AM





Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds