Suppose you want to test the focus traversal order of the
PersonEditorPanel
introduced in the previous
recipe. You want to ensure that focus travels from component to
component in the correct order as the user hits the tab key. To do
this, you write the following test:
public void testTabOrder( ) { JTextField firstNameField = this.tannerPanel.getFirstNameField( ); firstNameField.requestFocusInWindow( ); // simulate the user hitting tab firstNameField.transferFocus( ); // ensure that the last name field now has focus JTextField lastNameField = this.tannerPanel.getLastNameField( ); assertTrue("Expected last name field to have focus", lastNameField.hasFocus( )); }
As written, this test fails. First and foremost, the components must
be visible on screen before they can obtain focus. So you try
modifying your setUp( )
method as follows:
protected void setUp( ) throws Exception { this.emptyPanel = new PersonEditorPanel( ); this.tanner = new Person("Tanner", "Burke"); this.tannerPanel = new PersonEditorPanel( ); this.tannerPanel.setPerson(this.tanner); getTestFrame().getContentPane( ).add(this.tannerPanel, BorderLayout.CENTER); getTestFrame().pack( ); getTestFrame().show( ); }
This takes advantage of the JFrame
provided by our
base class, SwingTestCase
. When you run your test
again, it still fails! The initial focus never made it to the first
name field. Here is a partial solution:
public void testTabOrder( ) { JTextField firstNameField = this.tannerPanel.getFirstNameField( ); // make sure the first name field has focus while (!firstNameField.hasFocus( )) { getTestFrame().toFront( ); firstNameField.requestFocusInWindow( ); } // simulate the user hitting tab firstNameField.transferFocus( ); // ensure that the last name field now has focus JTextField lastNameField = this.tannerPanel.getLastNameField( ); assertTrue("Expected last name field to have focus", lastNameField.hasFocus( )); }
This approach keeps trying until the first name field eventually gains focus. It also brings the test frame to the front of other windows because, during testing, we found that the frame sometimes gets buried if the user clicks on any other window while the test is running. We discovered this by repeating our tests and clicking on other applications while the tests ran:
public static Test suite( ) { return new RepeatedTest( new TestSuite(TestPersonEditorPanel.class), 1000); }
We still have one more problem. When the test runs repeatedly, you
will notice that the test fails intermittently. This is because the
transferFocus( )
method does not occur
immediately. Instead, the request to transfer focus is scheduled on
the AWT event queue. In order to pass consistently, the test must
wait until the event has a chance to be processed by the queue. Example 4-15 lists the final version of our test.
Example 4-15. Final tab order test
public void testTabOrder( ) { JTextField firstNameField = this.tannerPanel.getFirstNameField( ); // make sure the first name field has focus while (!firstNameField.hasFocus( )) { getTestFrame().toFront( ); firstNameField.requestFocusInWindow( ); } // simulate the user hitting tab firstNameField.transferFocus( );// wait until the transferFocus( ) method is processed
waitForSwing( );
// ensure that the last name field now has focus JTextField lastNameField = this.tannerPanel.getLastNameField( ); assertTrue("Expected last name field to have focus", lastNameField.hasFocus( )); }
The waitForSwing( )
method is a new feature of our
base class, SwingTestCase
, that blocks until
pending AWT events like transferFocus( )
are
processed:
public void waitForSwing( ) { if (!SwingUtilities.isEventDispatchThread( )) { try { SwingUtilities.invokeAndWait(new Runnable( ) { public void run( ) { } }); } catch (InterruptedException e) { } catch (InvocationTargetException e) { } } }
Now, our test runs almost 100% of the time. When repeating the test thousands of times, you can make the test fail every once in a while by randomly clicking on other applications. This is because you take away focus just before the test asks the last name field if it has focus. This sort of problem is unavoidable, and illustrates one of the reasons why you should minimize your dependency on Swing tests.
The most valuable lesson of this recipe is the technique of repeating your graphical tests many thousands of times until all of the quirky Swing threading issues are resolved. Once your tests run as consistently as possible, remove the repeated test. Also, while your tests are running, avoid clicking on other running applications so you don’t interfere with focus events.
Chapter 11 provides some references to Swing-specific testing tools.
Get Java Extreme Programming Cookbook now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.