[TFUI] Strict User Simulations

Phlip <phlipcpp-/[email protected]>
Newsgroups gmane.comp.programming.test-first-user-interfaces
Message-ID <[email protected]>
If you research your GUI Toolkit’s event queue, and
its exact operation, you can often uncover its
mechanism to forward raw inputs. Invest this research
into test fixtures that enable the kinds of fake input
events that test cases for your application require.
Few projects need such a high level of simulation. Use
this technique if it's the easiest way, or if your
project's input is very detailed, such as an editor
that can handle letters with complex shapes.

The following example code uses a rare and exotic
language called "Java", invented by James Gosling. The
example reveals a GUI test rig called "Jemmy",
invented by Aleandre Iline. It exercises complete
Event Queue Regulation for the Swing GUI Toolkit. The
small penalty for these easy features is windows that
flicker and animate their behaviors during tests. 

Special thanks to Andrew de Torres for presenting this
to the XP San Diego Users Group, and for allowing me
to use it here. I typeset some comments, for clarity.
The target of this test is a simple Swing form:

package src.com.ureach.detorres.jemmytest;

import java.awt.GridLayout;
import java.awt.event.*;
import javax.swing.*;

/**
  * This simple "Hello World" application demonstrates
testing Swing applications with JUnit (by Kent Beck
and Erich Gamma) and Jemmy. The application consists
of a JFrame with a JTextField, JButton, and JLabel. 
Initially the field is blank, the button is disabled,
and the label is blank.  When you type "Enable button"
into the field, the button is enabled.  When you push
the button, a dialog displays. When you acknowledge
the dialog, the label displays a status message, and
the field and button are disabled.

 * @author  Andrew de Torres, detorres-HTy/[email protected]
 */

public class HelloWorld 
{

    private JTextField textfield;

    private JButton button;

    private JLabel label;

    public HelloWorld() 
    {
        textfield = new JTextField(20);
        button = new JButton("Push me");
        button.setEnabled(false);

        label = new JLabel();

        JPanel panel = new JPanel(new GridLayout(3,
1));
        panel.add(textfield);
        panel.add(button);
        panel.add(label);

        final JFrame frame = new JFrame("Hello,
World!");
        frame.setContentPane(panel);

        textfield.addKeyListener(new KeyAdapter() {

            public void keyReleased(final KeyEvent
evt) {
                if (textfield.getText().equals("Enable
button")) {
                    button.setEnabled(true);
                } else {
                    button.setEnabled(false);
                }
            }
        });

        button.addActionListener(new ActionListener()
{

            public void actionPerformed(final
ActionEvent evt) {
                JOptionPane.showMessageDialog(frame, 
                        "You're almost done.",
                        "Hi!",
JOptionPane.INFORMATION_MESSAGE);
                label.setText("You're done.");
                textfield.setEnabled(false);
                button.setEnabled(false);
            }
        });

        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.show();
    }

    public static void main(String[] args) {
        new HelloWorld();
    }
}

// eof "HelloWorld.java"

Jemmy solves a common problem simulating events.
Suppose a button provides a .push() method, so
production code can simulate user input. However,
suppose that .push() method worked even if its button
were disabled. When test cases rely on the .push()
method, alone, its results might mislead. If a button
was disabled and should be enabled, then if a test
called .push() and recorded an enabled response, the
test would not catch that bug.

// HelloWorldTestUI.java

package test.com.ureach.detorres.jemmytest;

import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.WindowConstants;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.netbeans.jemmy.*;
import org.netbeans.jemmy.operators.*;

import src.com.ureach.detorres.jemmytest.HelloWorld;

/*  JUnit/Jemmy test for HelloWorld.  This class
demonstrates the basics of testing a Swing application
using Jemmy inside of JUnit.

 * @author Andrew de Torres, detorres-HTy/[email protected]
 * 
 */

public class HelloWorldTestUI 
    extends TestCase 
{
    public HelloWorldTestUI(String testName) {
        super(testName);
    }

    class Flag {  boolean flag;  }
    final Flag flag = new Flag();

    public void reveal(final JFrameOperator frame) 
            throws InterruptedException 
    {
       
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);

        frame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                flag.flag = true;
                frame.removeWindowListener(this);
            }
        });

        while (!flag.flag)
            Thread.sleep(100);

    } //  by Timothy Wall

    /**
     * Our single test method which tests everything.
     */
    public void testIt() throws InterruptedException 
    {
        // Turn off Jemmy output.
       
JemmyProperties.setCurrentOutput(TestOut.getNullOutput());

/* 	Because Jemmy operates a real event queue, it must
wait for streams of messages to communicate between
the platform and the GUI Toolkit. Some controls won’t
finish painting until after an indefinite number of
messages, so Jemmy relies on small timeouts. For each
kind of control, test cases must declare how long they
will wait before grabbing that control. A complete
test rig would configure these defaults in a fixture
such as setUp().
*/
    
        // Shorten timeouts so things fail quicker.
       
Timeouts.setDefault("FrameWaiter.WaitFrameTimeout",
5000);
       
Timeouts.setDefault("DialogWaiter.WaitDialogTimeout",
5000);
       
Timeouts.setDefault("ComponentOperator.WaitComponentTimeout",
5000);

        // Start the application under test.
        HelloWorld.main(new String[0]);
    
/*	Jemmy wraps each kind of target control in an
Operator object. This test fixture mediates between
test cases and simulated user inputs.
*/

        final JFrameOperator frame = new
JFrameOperator(
                                              "Hello,
World!");

        // Find 1st (0th) JTextField in frame.
        JTextFieldOperator textfield = new
JTextFieldOperator(frame, 0);
        assertTrue("textfield is enabled",
textfield.isEnabled());
        assertTrue("textfield is editable",
textfield.isEditable());
    //    reveal(frame);

        // Find "Push me" button.
        JButtonOperator button = new
JButtonOperator(frame, "Push me");
        assertTrue("button is disabled",
!button.isEnabled());        
        JLabelOperator label = new
JLabelOperator(frame, 0);
        assertEquals("label is blank", "",
label.getText());
    
/*  	If this button.push() were not commented out, it
would do nothing. Jemmy Operators wraps each kind of
target control in an Operator object. This test
fixture mediates between test cases and simulated user
inputs.
*/

      //  button.push();

        // Simulate typing text.   
        textfield.typeText("Enable button");
        assertTrue("button is enabled",
button.isEnabled());
        
        // Simulate pushing button.    
        button.push();

        // Note partial match - dialog title is "Hi!".
   
        JDialogOperator dialog = new
JDialogOperator("Hi");
        new JButtonOperator(dialog, "OK").push();
        assertEquals("label changed", "You're done.",
label.getText());
        assertTrue("textfield is disabled",
!textfield.isEnabled());
        assertTrue("button is disabled",
!button.isEnabled());

        // Throw in a dispose here in case we run with
the JUnit GUI (see main
        // method).
        frame.dispose();
    }

    public static Test suite() 
    {
        TestSuite suite = new
TestSuite(HelloWorldTestUI.class);
        return suite;
    }
  
/*	The main() function runs the test using the JUnit
test runner. If the single argument "swing" is
specified, the GUI test runner is used. Otherwise, the
command line test runner is used.
   
   * @param _args command line args: ["swing"]
   */
    public static void main(String[] _args) 
    {
        String[] testCaseName = 
                    { HelloWorldTestUI.class.getName()
};

        //      _args[0] = "swing";

        if (_args.length == 1 &&
_args[0].equals("swing")) {
           
junit.swingui.TestRunner.main(testCaseName);
        } else {
           
junit.textui.TestRunner.main(testCaseName);
        }
    }
}

//eof "HelloWorldTestUI.java"

An off-the-shelf GUI test rig should provide a
balanced set of generic fixtures that cover common GUI
aspects. The length of the case testIt() reveals the
importance of growing new, application-specific
fixtures. If our little project had more tests (and a
reason to exist), then Extract Method Refactor would
grow new fixtures, and these would shrink each test
case, and make new ones easier to write. 

=====
Phlip
  http://industrialxp.org/community/bin/view/Main/TestFirstUserInterfaces


		
__________________________________
Do you Yahoo!?
Yahoo! Mail Address AutoComplete - You start. We finish.
http://promotions.yahoo.com/new_mail 


------------------------ Yahoo! Groups Sponsor --------------------~--> 
Yahoo! Domains - Claim yours for only $14.70
http://us.click.yahoo.com/Z1wmxD/DREIAA/yQLSAA/nhFolB/TM
--------------------------------------------------------------------~-> 

To unsubscribe, email:
TestFirstUserInterfaces-unsubscribe-hHKSG33TihhbjbujkaE4pw@public.gmane.org
 
Yahoo! Groups Links

<*> To visit your group on the web, go to:
    http://groups.yahoo.com/group/TestFirstUserInterfaces/

<*> To unsubscribe from this group, send an email to:
    TestFirstUserInterfaces-unsubscribe-hHKSG33TihhbjbujkaE4pw@public.gmane.org

<*> Your use of Yahoo! Groups is subject to:
    http://docs.yahoo.com/info/terms/
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.