ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


JDemo: Interactive Testing Refactored JDemo: Interactive Testing Refactored

by Markus Gebhard
09/08/2004

Today's software development is unthinkable without automated testing; e.g., writing unit tests using the JUnit framework. But there still are some aspects that cannot be tested automatically. An example is the appearance of user interface components:

Interactive tests are indispensable when a software developer wants to answer these questions.

This article will introduce the JDemo framework and its techniques for writing code for interactive testing. It will also show the benefits that can be gained from writing demo code.

Example

Let's have a look at a simple example component. Figure 1 shows the API of MyDice, a Swing component for showing a die having a specified or random value.

API for MyDice class
Figure 1. MyDice example class for showing a die as a Swing component

Instead of only looking at the API, we are much more interested in what the die actually looks like on the screen. A very popular approach for this is to add a simple main method to the component class:

public static void main(String[] args) {
  JComponent diceComponent = new MyDice();
  JFrame frame = new JFrame("MyDice");
  Container contentPane = frame.getContentPane();
  contentPane.setLayout(new BorderLayout());
  contentPane.add(diceComponent, BorderLayout.CENTER);
  frame.setDefaultCloseOperation(
	JFrame.EXIT_ON_CLOSE);
  frame.pack();
  frame.show();
}

Running this class will open a frame showing a die on the screen. The die will have a random value, since the default constructor from the MyDice class is being used. A screenshot of the frame is shown in Figure 2.

Screenshot of MyDice component showing a dice having a random value
Figure 2. Screenshot of a MyDice component

Demo Code Refactored

The main method solution in the previous section has some serious drawbacks. Having snippets of this kind of demonstration code spread all over our production code gives a bad-code smell. The snippets contain lots of duplication, sometimes add unnecessary dependencies, and are likely to break.

A better approach is to move the demo code to separate classes. Recently, Jonathan Simon described a technique called simulators. Simulators are special classes, written for graphically testing, or simulating, a component in various conditions. Usually, simulator classes are written from scratch and there is no assistance by frameworks or tools. Here and now we want to go a step further. First, let's write down a list of what we would like to have:

Removing anything non-essential from the demo code above leaves two simple lines of code, looking somewhat like this:

JComponent diceComponent = new MyDice();
show(diceComponent);

We want to create a dice component and then show it. Anything else does not matter much, and so we should not have to care about it. Using the JDemo framework, writing demos is just that easy.

Writing Our First JDemo Demo Cases

Among others, JDemo provides an abstract base class SwingDemoCase, which contains many convenience methods for Swing-based demos. One of these methods is for displaying a JComponent object on the screen. So we can implement a demo class for the MyDice component this way:

package de.jdemo.examples.dice.demo;

import de.jdemo.extensions.SwingDemoCase;

public class MyDiceDemo extends SwingDemoCase {

  public void demo6Dice() {
    JComponent diceComponent = new MyDice(6);
    show(diceComponent);
  }

  public void demoRandomDice() {
    JComponent diceComponent = new MyDice();
    show(diceComponent);
  }
}

There are two different ways to instantiate MyDice objects, so I have added two demos to the MyDiceDemo class: 6Dice and RandomDice.

As can be seen in our little example, JDemo demo cases must be no-argument public methods with a void return type. Their names start with demo...() and they are implemented in subclasses of one of the abstract demo case implementations, which are provided by the framework. Since we want to demonstrate a Swing-based component, I have chosen SwingDemoCase as the superclass for the demo implementation. Later in this article, we will have a look at the other JDemo base classes that are available.

The JDemo framework provides a Swing-based DemoRunner application for loading and executing demos. It can be started from the command line by specifying the name of the demo class as argument:

java de.jdemo.swingui.DemoRunner de.jdemo.examples.dice.demo.MyDiceDemo

Of course, we have to make sure that jdemo.jar and the MyDice example (contained in the examples folder of the JDemo distribution) are in the CLASSPATH.

Figure 3 shows the DemoRunner when launched with our MyDiceDemo class.

JDemo DemoRunner launched on MyDice demo
Figure 3. JDemo DemoRunner running the MyDice demo

All available demos are listed as tree view in the upper half of the application. They can be run by double-clicking on the tree items or by choosing one of the various actions available in the context menu of each entry. The execution list in the lower half shows the state of executed demos. In Figure 3, you can see that the Random Dice demo is running. Figure 4 shows the frame JDemo has opened on the screen in order to show the component.

Random Dice demo executed in DemoRunner
Figure 4. Output of the Random Dice demo executed in JDemo

Having a look back to the wish list from the previous section, we can see that our demo implementation meets all of the requirements.

JUnit as a Role Model

You already might have realized that JDemo DemoCases are similar to JUnit TestCase implementations. The reason for this is that the framework is based on the concepts, simplicity, and source code of JUnit. Developers familiar with unit testing will only have to learn a few things in order to use JDemo. Here is a list of the main aspects and differences of which one has to be aware:

It has often has been claimed that unit tests are good example code. But in my opinion, in most cases this just isn't true. In unit tests, I almost never saw suitable examples for how to use the provided API. This is because unit tests usually do not implement practical examples. Demos, on the other hand, are practical examples, so it is much more likely that good example code will be found there. In fact, practice has shown that combining test-driven development with writing demos is a great combination, with JUnit and JDemo complementing each other.

Organizing Demos Hierarchically

Just like JUnit test cases, JDemo demo cases can be arranged in suites. Demo suites use the composite pattern in order to create a new demo containing multiple demo classes. A demo suite can then be passed to the DemoRunner in order to have more than one demo class available for execution.

Here is a simple example:

package de.jdemo.examples.dice.demo;

import de.jdemo.framework.DemoSuite;
import de.jdemo.framework.IDemo;

public class AllDemos {

  public static IDemo suite() {
    DemoSuite suite = new DemoSuite(
	  "All Demos for MyDice");
    suite.addDemo(new DemoSuite(MyDiceDemo.class));
    return suite;
  }
}

This example creates a demo suite only containing our MyDiceDemo demo class. Of course, demo suites themselves can then be added to other suites, and so on. A well-maintained set of demos will then form a tree hierarchy. Figure 5 shows a demo tree for a more complex example application.

Tree of demos for a real world scenario
Figure 5. A well-structured tree of demos for a more realistic software example

Demos as Index to Existing Software Libraries

Let's have another look at the demos in Figure 5. Such a complete set of demos can also be very valuable when you have to work on existing code libraries. You do not need to start the complete application, log in to a database, and load example data, all in order to have a look at the export dialog. Instead you just start the demos and select the relevant component from the DemoRunner.

You also will not have to crawl the source code to find the relevant code; with JDemo, you can link the demo source code to the DemoRunner application. Let's try this for our MyDice example by specifying the path to the source code as sourcepath when starting the DemoRunner application:

java de.jdemo.swingui.DemoRunner -sourcepath . de.jdemo.examples.dice.demo.MyDiceDemo

Similar to the CLASSPATH, the sourcepath is a list of directories or .zip or .jar files. Here we have specified the current directory to contain the source code. Now we have a context menu item, Show source code, at each demo in the DemoRunner (see Figure 6).

Show source code
Figure 6. A menu item for showing the source code from within the DemoRunner

By default, the JDemo DemoRunner shows the source-code syntax highlighted (using the Java2Html library) in a separate window (see Figure 7). Note that when you are using the JDemo plugin available for the Eclipse IDE, the source code will be shown directly in the development environment.

Syntax highlighted demo source code
Figure 7. Syntax highlighted source code as shown by the DemoRunner

Automatically Capturing Demo Output

The JDemo framework provides many options and advanced techniques that cannot all be covered in this article. However I want to give a quick introduction to one of the most interesting techniques: capturing demo output automatically.

As each demo shows its object for demonstration using a show...() method from the framework, it is not very hard to capture this output automatically. For GUI-based demos, the captured output is a screenshot taken by using the java.awt.Robot class.

The following Ant build script shows how we can take a screenshot from our MyDice demo:

<project default="screenshot">
  <taskdef name="demoGuiCapture"
    classname=
      "de.jdemo.capture.anttasks.GuiDemoCaptureTask"
    classpath="jdemo.jar"
  />

  <target name="screenshot">
    <demoGuiCapture
      demoId=
        "de.jdemo.examples.dice.demo.MyDiceDemo:demo6Dice"
      outputFile="dice_screenshot.png"
      includeTitle="false"
    />
  </target>
</project>

First a new Ant task, demoGuiCapture, is defined. Again, we have to make sure that jdemo.jar is on the specified CLASSPATH. The screenshot target uses the demoGuiCapture task for taking a screenshot from the 6Dice demo. Also note that by setting the includeTitle option to false, we only capture the MyDice component, not the complete frame. Figure 8 shows the captured image.

Screenshot automatically taken with JDemo
Figure 8. Screenshot automatically taken of the 6Dice demo

As a matter of fact, all of the screenshots in this article were taken automagically by using JDemo (even Figure 6, which actually is a composition of two demos).

Beyond Swing

JDemo is not limited to Swing-based demos. The framework comes with four base classes for different kinds of demos:

JDemo base class Description
de.jdemo.framework.DemoCase Demos showing text (java.lang.CharSequence) or File objects.
de.jdemo.extensions.AwtDemoCase Demos based on components from the Java AWT (Abstract Windowing Toolkit).
de.jdemo.extensions.SwingDemoCase Swing-based demos--as described in this article. Since Swing is based on AWT, SwingDemoCase subclasses AwtDemoCase.
de.jdemo.extensions.SwtDemoCase Demos based on the Eclipse SWT (Standard Widget Toolkit).

In practice, when writing demos, you will probably subclass one of the provided classes directly. Usage examples can be found in the examples folder of the JDemo download package. Their API is described in detail in the JavaDoc API documentation and the JDemo online documentation.

Wrap-Up

In this article we have learned how the JDemo framework can be used to organize demonstration and example code by writing demo case classes. Like JUnit test cases, JDemo demo cases can be organized in suites and run from the provided DemoRunner application.

We also have seen that demos are not only useful during the development of a software component, but can also be very valuable later. Demos can be used to directly access all of the software components in a large library. They also provide example code for how to use the API properly. As an additional benefit, GUI demos can be used for automatically taking screenshots; e.g., for documentation purposes.

This article could only show a few aspects of the framework. In order to learn more about it and its capabilities, I recommend reading the detailed online documentation or just checking JDemo out on your next software project.

Resources

Markus Gebhard works as software engineer at disy Informationssysteme GmbH, in Karlsruhe, Germany.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.