[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 Toolkits 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 wont 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/