Chapter 7. Displaying the Results

We left the last chapter having invoked the tests. In this chapter, we will

  • Design the protocol for collecting test results

  • Outline what it takes to actually run the tests

  • Display the test results

How are we going to display the results? The JUnit plug-in class should know nothing about how results are displayed. So, what if the plug-in class broadcasts the results of tests that are running?

The Observer pattern is useful when you have loosely coupled communication patterns:

  • You don't care in what order the receivers receive the messages.

  • You don't have to return any results to the sender.

  • You don't know a priori how many objects will want to receive the message (see Figure 7.1).

    One Subject Notifies Several Observers

    Figure 7.1. One Subject Notifies Several Observers

These conditions hold in our case. Tests will run and various observers will respond to notifications of progress (see Figure 7.2). The role of the Observer will be played by our RunTestAction at first, later also by a view, a tabular report view, and so on. The Observer Interface will contain the set of progress messages: “The tests are starting,” “The tests are done,” “A test has started,” and “A test has failed.”

One TestRunner Notifies Its Observers

Figure 7.2. One TestRunner Notifies Its Observers

How would this look in the code we've written so far?

  1. Before we ask the JUnit plug-in to run the tests, we should connect a listener to the plug-in. The plug-in acts as a Subject in the Observer pattern.

  2. The listener will be notified as tests run.

  3. When the last test runs, the listener will pop up a dialog containing the results.

  4. We will disconnect the listener.

First, we need to change the calling sequence in the action. Before we run tests, we need to register a listener, and afterward we need to unregister a listener. What object should play the part of the listener? A clever solution is to have our ActionDelegate also implement ITestRunListener. However, this requires us to make the listener methods public in RunTestAction, and we don't really want to publicize the test listener methods as part of the action. Instead, we'll implement the listener as a static inner class. Let's start with the code to register the listener in the run() method:

Example . org.eclipse.contribution.junit/RunTestAction

public void run(IAction action) {
  if (! (selection instanceof IStructuredSelection))
    return;
  IStructuredSelection structured= (IStructuredSelection) selection;
  IType type= (IType) structured.getFirstElement();
  ITestRunListener listener= new Listener();
  JUnitPlugin.getPlugin().addTestListener(listener);
  JUnitPlugin.getPlugin().run(type);
  JUnitPlugin.getPlugin().removeTestListener(listener);
}

The listener will need to respond to the progress of the tests. We'll represent the messages being broadcast as an interface:

Example . org.eclipse.contribution.junit/ITestRunListener

public interface ITestRunListener {
  void testsStarted(int testCount);
  void testsFinished();
  void testStarted(String klass, String method);
  void testFailed(String klass, String method, String trace);
}

Next we implement the Listener as a static inner class in RunTestAction. If we want to pop up a dialog with “Pass” or “Fail” when the tests finish, we need to set a flag when the tests start and clear it if a test ever fails:

Example . org.eclipse.contribution.junit/RunTestAction$Listener

public static class Listener implements ITestRunListener {
  private boolean passed= true;
  public void testsStarted(int testCount) {
  }
  public void testsFinished() {
    String message= passed ? "Pass" : "Fail";
    MessageDialog.openInformation(null, "Test Results", message);
  }
  public void testStarted(String klass, String method) {
  }
  public void testFailed(String klass, String method,
     String trace) {
    passed= false;
  }
}

We really should have passed in a parent shell as the first parameter to the MessageDialog, so the dialog shows up relative to the workbench window. For now, this is good enough.

We need to implement adding and removing the test listeners:

Example . org.eclipse.contribution.junit/JUnitPlugin

private List listeners= new ArrayList();

public void addTestListener(ITestRunListener listener) {
  listeners.add(listener);
}

public void removeTestListener(ITestRunListener listener) {
  listeners.remove(listener);
}

We will create a TestRunner to run our tests for us. For now, we can't imagine how your understanding of Eclipse would be helped by seeing the details of starting a new virtual machine and communicating with it via sockets. See Appendix A for the details. If you are following along in Eclipse, add the code for TestRunner and SocketTestRunner now. You will also need to add org.eclipse.jdt.launching, org.eclipse.debug.core, and org.junit as required plug-ins.

With the TestRunner in place we can go back to the “TODO” comment in JUnitPlugin.run() and implement the method body:

Example . org.eclipse.contribution.junit/JUnitPlugin

public void run(IType type) throws CoreException {
  new TestRunner().run(type);
}

Our TestRunner will call back to the JUnitPlugin to ask it to broadcast progress to all the listeners. Here is the simple version of these four methods:

Example . org.eclipse.contribution.junit/JUnitPlugin

  public void fireTestsStarted(int count) {
  for (Iterator all= getListeners().iterator(); all.hasNext();) {
    ITestRunListener each= (ITestRunListener) all.next();
    each.testsStarted(count);
  }
}
public void fireTestsFinished() {
  for (Iterator all= getListeners().iterator(); all.hasNext();) {
    ITestRunListener each= (ITestRunListener) all.next();
    each.testsFinished();
  }
}
public void fireTestStarted(String klass, String methodName) {
  for (Iterator all= getListeners().iterator(); all.hasNext();) {
    ITestRunListener each= (ITestRunListener) all.next();
    each.testStarted(klass, methodName);
  }
}
public void fireTestFailed(
    String klass, String method, String trace) {
  for (Iterator all= getListeners().iterator(); all.hasNext();) {
    ITestRunListener each= (ITestRunListener) all.next();
    each.testFailed(klass, method, trace);
  }
}

For now, getListener() is a simple accessor for the listeners field.

We've implemented the listener mechanism, but we did it the usual dynamic Java way. In Eclipse, as much as possible you invite others to share in the benefits of your plug-ins. We'd like to make it easy for others to register their own test listeners. In the next chapter, we'll see how to do that. Reviewing, we

  • Designed a listener-based protocol for collecting test results

  • Displayed results using a listener inside our menu action

  • Implemented adding and removing listeners in the plug-in class

Forward Pointers

  • JFace provides a utility class org.eclipse.jface.util.ListenerList. You can use it to maintain a list of listeners.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset