Skip to main content

If you don't have an IBM ID and password, register here.

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. This profile includes the first name, last name, and display name you identified when you registered with developerWorks. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

All information submitted is secure.

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

Diagnosing Java Code: The Liar View bug pattern

Be your GUI's best friend and expose the Liar View

Eric Allen (eallen@cyc.com), Lead Java Developer, Cycorp, Inc.
Eric Allen has an A.B. in computer science and mathematics from Cornell University. He is currently the lead Java software developer at Cycorp, Inc., and a part-time graduate student in the programming languages team at Rice University. His research concerns the development of formal semantic models of Java, and extensions of Java, both at the source and bytecode levels. Currently, he is implementing a source-to-bytecode compiler for the NextGen programming language, an extension of Java with generic run-time types. Contact Eric at eallen@cyc.com.

Summary:  GUIs are generally designed with a model-view-controller architecture in which the view is decoupled from the model. The separation presents a challenge to automated testing because it's difficult to verify that a state change in the model is reflected appropriately in the view -- it spawns the infamous "Liar View." This installment of Diagnosing Java Code examines the Liar View bug pattern.

View more content in this series

Date:  23 Apr 2001
Level:  Introductory

Comments:  

Liar, liar!

Picture this: you've crafted a golden GUI program designed for a distributed system, all that the client asked for and then some. You've run it through an automated test suite -- because the number of invariants is astronomical, automated testing is a must. It came back with a clean bill of health.

The pressure is on to deliver the GUI, but being the exacting programmer that you are, you fire it up for one last manual test only to discover erroneous behavior -- behavior that should have been caught by the automated tests. If only you could have prevented this situation. Well, you can.


The Liar View bug pattern

Good debugging starts with good testing. And with the vast number of invariants that must be checked for a GUI program, automated testing is essential. But sometimes, despite having passed its test suite, a program will, upon manual inspection, exhibit erroneous behavior that should have been discovered by one of the tests.

Fast-track to the code

Listing 1. JTable and Table Model A simple GUI to illustrate how the Liar View occurs

Listing 2. Checking view, model, and row content Making assertions tell the truth

Such behavior is common in distributed and multithreaded systems. In these cases, the non-deterministic nature of the program is often the cause. But in the case of GUIs, there is another common cause -- the Liar View bug pattern.


The symptoms

Most GUI program tests, like program tests in general, follow this structure:

  • Start the program.
  • Check some aspect of its state.
  • Attempt to modify the state.
  • Check that the state was modified as intended.

But as I've mentioned, sometimes a manual inspection of the run-time program behavior will contradict the successful results of the tests: queues may appear on the screen containing elements that testing (supposedly) confirmed were deleted; and objects may contain stale data that was reportedly updated.

Bugs like these may cause us to question our sanity or, worse yet, fall into a Kantian skepticism of the validity of reason itself.

Don't let this happen to you. When used as directed, reason really does work. And, despite reports to the contrary, it is the rare programmer who permanently loses his sanity while coding (permanently being the operative word).


The cause

A key to finding these bugs is to realize that at least part of the bug may be in the test suite.

The most common place for a bug to occur in a test on a GUI program is in the last step: checking that the state of the program was modified as intended. The reason is that GUIs are generally designed with a model-view-controller (MVC) architecture. Indeed, the Swing class library builds this architecture into the structure of the GUI classes themselves.

In an MVC architecture, the internal state of the program is kept in the model. The view responds to events signifying a new state of the model and updates the screen image accordingly. The controller connects these two components together.

The advantage of this architecture is that it decouples the view from the model, so that the implementation of either one can change independently. But it poses a challenge to automatic testing methods: it can be difficult to verify that a state change in the model is reflected appropriately in the view. When there is a discrepancy between the two, we have an instance of the Liar View bug pattern.

For example, consider the following simple GUI. It displays the contents of a list of elements as it is updated. The main method of the Controller class is used as a simple test. In a real application, I'd move this method into a separate test class and hook it into JUnit (see Resources).

I've added a pause() method, and a PAUSE field to allow us to put the test into slow motion and manually inspect each event as it occurs.

			
		

import java.awt.*;
import java.awt.event.*;
import java.util.Vector;
import javax.swing.*;

public class Controller {

  private static final int PAUSE = 1;

  private static void assert(boolean assertion) {
    if (! assertion) {
      throw new RuntimeException("Assertion Failed");
    }
  }

  private void pause() {
    try {
      synchronized (this) { 
	wait(PAUSE); 
      }
    }
    catch (InterruptedException e) {
    }
  }

  public static void main(String[] args) {

    Controller controller = new Controller();
    JFrame frame = new JFrame("Test");
    Model model = new Model();
    JList view = new JList(model);

    view.setPreferredSize(new Dimension(200,100));
    frame.getContentPane().add(view);
    frame.pack();    
    frame.setVisible(true);
    assert(model.getSize() == 0);
 
    controller.pause();
    model.add("test0");

    controller.pause();
    model.add("test1");

    controller.pause();
    assert(model.getSize() == 2);

    controller.pause();
    model.remove(0);

    controller.pause();
    assert(model.getSize() == 1);

    controller.pause();
    System.exit(0);
  }
}

class Model extends AbstractListModel {

  private Vector elements = new Vector();

  public synchronized Object getElementAt(int index) {
    return elements.get(index);
  }
   
  public synchronized int getSize() {
    return elements.size();
  }

  public synchronized void add(Object o) {
    int index = this.getSize();
    this.elements.add(o);
    this.fireIntervalAdded(this, index, index);
  }

  public synchronized void remove(int index) {
    this.elements.remove(index);
  }
}

You may have noticed that there is a serious bug in this code. If we run the test case, all assertions succeed, indicating that items are added and removed from the list appropriately. But if we slow things down by, say, setting PAUSE to 1000, we can inspect the test run manually. And guess what? We notice that no items are ever removed in the view.

The reason that the view is not updated is that the remove() method in class Model never calls fireIntervalRemoved() to notify any listeners that the state of the model has changed.

But all the assertions in our test method succeed. Why? Because these assertions check for changes in the model, not the view. Because the model is updated appropriately, the missing event firing is not detected by the assertions.


Cures and preventions

One way to prevent this bug pattern is to check explicit properties of the view only after changing the state of the model. Although this technique limits the properties we can check to what is provided in the view, at least the assertions reflect what's really happening on screen.

For example, we could rewrite Controller.main as follows:

		
import java.awt.*;
import java.awt.event*;
import java.util.Vector;
import java.swing.*;

public class Controller {

	. . .
	
  public static void main(String[] args) {

    Controller controller = new Controller();
    JFrame frame = new JFrame("Test");
    Model model = new Model();
    JList view = new JList(model);

    view.setPreferredSize(new Dimension(200,100));
    frame.getContentPane().add(view);
    frame.pack();    
    frame.setVisible(true);
    assert (model.getSize() == 0);
    controller.pause();

    boolean toggle = model.toggle;
    model.add("test0");
    assert ( toggle == ! model.toggle);

    controller.pause();
    toggle = model.toggle;
    model.add("test1");
    assert ( toggle == ! model.toggle);

    controller.pause();
    assert(model.getSize() == 2);
    view.setSelectedIndex(0);
    assert(view.getSelectedValue().equals("test0"));

    controller.pause();
    toggle = model.toggle;
    model.remove(0);
    assert(toggle == ! model.toggle);

    controller.pause();
    assert(model.getSize() == 1);
    view.setSelectedIndex(0);
    assert(view.getSelectedValue().equals("test1"));

    controller.pause();
    System.exit(0);
  }
}

  class Model extends AbstractListModel {

    boolean switch = false;

    private Vector elements = new Vector();

    ...

    public void fireIntervalAdded(AbstractListModel m, int start, int end) {
      super.fireIntervalAdded(m,start,end);
      this.switch = ! this.switch;
    }

    public void fireIntervalRemoved(AbstractListModel m, int start, int end) {
      super.fireIntervalAdded(m,start,end);
      this.switch = ! this.switch;
    }
    
  }

By using setSelectedIndex() and getSelectedIndex(), we perform a slightly different, but much improved, test on the program. Not only does the modified test check the view rather than the model, it also checks the content of selected rows, rather than just the number of rows.

Another way to check the view directly is to use the Java Robot class (introduced in the Java 1.3 API -- see Resources) to actually automate the physical manipulation of a GUI with the mouse and keyboard.

The Robot class also lets you take snapshots of subsections of the screen, allowing you to build tests based on the actual physical layout of a GUI view. Of course, this ability can be a disadvantage if the physical layout is not as stable as the logical structure of the view. It can be painful to have to rewrite several tests every time the physical layout changes. Therefore, I recommend using the Robot class as a testing tool for mature GUIs whose view won't change very often. To test the logical aspects, call methods on the view like we do above.

A final caveat: beware of methods in view objects that simply trampoline calls back to the model. Doing so can quickly introduce Liar Views. JTables, in particular, contain many such methods.


Wrapup

Here's the breakdown of this week's bug pattern:

  • Pattern: Liar View
  • Symptoms: A GUI program passes a suite of tests but then exhibits behavior that should've been ruled out by those tests.
  • Cause: The tests check aspects of the model rather than the view.
  • Cures and preventions: Check aspects of the view.

Bit by bit we're working our way through solutions to the most common (and most frustrating) bugs, but we still have a lot of ground to cover. Next time, we'll tackle a more subversive bug: saboteur data, data that is perfectly benign ... until it's accessed. Stay tuned!


Resources

About the author

Eric Allen has an A.B. in computer science and mathematics from Cornell University. He is currently the lead Java software developer at Cycorp, Inc., and a part-time graduate student in the programming languages team at Rice University. His research concerns the development of formal semantic models of Java, and extensions of Java, both at the source and bytecode levels. Currently, he is implementing a source-to-bytecode compiler for the NextGen programming language, an extension of Java with generic run-time types. Contact Eric at eallen@cyc.com.

Report abuse help

Report abuse

Thank you. This entry has been flagged for moderator attention.


Report abuse help

Report abuse

Report abuse submission failed. Please try again later.


developerWorks: Sign in

If you don't have an IBM ID and password, register here.


Forgot your IBM ID?


Forgot your password?
Change your password


By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. This profile includes the first name, last name, and display name you identified when you registered with developerWorks. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

Choose your display name

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

(Must be between 3 – 31 characters.)


By clicking Submit, you agree to the developerWorks terms of use.

 


Rate this article

Comments

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=10531
ArticleTitle=Diagnosing Java Code: The Liar View bug pattern
publish-date=04232001
author1-email=eallen@cyc.com
author1-email-cc=

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

For articles in technology zones (such as Java technology, Linux, Open source, XML), Popular tags shows the top tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), Popular tags shows the top tags for just that product zone.

For articles in technology zones (such as Java technology, Linux, Open source, XML), My tags shows your tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), My tags shows your tags for just that product zone.

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).