From: Steve F. <sm...@us...> - 2002-08-04 01:41:51
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv30310/doc/xdocs Modified Files: testing_guis_1.xml Log Message: More on GUI testing Index: testing_guis_1.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/testing_guis_1.xml,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- testing_guis_1.xml 3 Aug 2002 22:23:10 -0000 1.2 +++ testing_guis_1.xml 4 Aug 2002 01:41:47 -0000 1.3 @@ -18,7 +18,7 @@ <chapter> <title>A Search Dialogue</title> - <section> + <sect1> <title>Requirements</title> <para> Our customer has asked us to write our favourite demonstration application, a search dialogue that takes @@ -83,9 +83,9 @@ we can see that we're always moving forward. What we don't do is write the whole user interface and model seperately and then try to glue them to together at the end. </para> - </section> + </sect1> - <section> + <sect1> <title>First test: a search with no results.</title> <para> Sometimes the hardest thing in test-driven development is getting started (a bit like writing this book). @@ -194,9 +194,9 @@ application. It's not (yet) great software, but it shows some progress and helps to organise our thoughts. It's also executable, so we have very early, repeatable feedback. </para> - </section> + </sect1> - <section> + <sect1> <title>Finishing the first test</title> <para> Now we've assembled some core infrastructure, we can finish off the test with some meaningful behaviour. @@ -309,9 +309,9 @@ <screenshot><mediaobject> <imageobject><imagedata fileref="images/gui_example2.gif" format="gif" /></imageobject> </mediaobject></screenshot> - </section> + </sect1> - <section> + <sect1> <title>What have we learned</title> <para> User interface testing is messy, but not impossible. Graphical frameworks are made up of code, just like @@ -328,22 +328,24 @@ a system is in development—they might even be worked on by different people—and most likely you don't want your test suite to collapse because someone has reworked the way it looks. </para> - </section> - </chapter> + </sect1> + </chapter> <!-- A Search Dialogue --> <chapter> <title>Second test: search with one result.</title> - <para> - Now we'll extend our application a little. In most situations, the interesting cases are <emphasis>0</emphasis>, - <emphasis>1</emphasis>, and <emphasis>multiple</emphasis> results. - We've covered no results in our first test, so now we'll look at a single result. This means that we can - concentrate on how the mechanisms for searching and displaying fit together, without worrying (for the moment) - about things like which collection to use or how to manage scrollbars. Our test needs to setup the single - result to return, check that the result has been passed through to the output component, and check that the - status field is empty, something like this: - </para> + <sect1> + <title>Introducing a Directory</title> + <para> + Now we'll extend our application a little. In most situations, the interesting cases are <emphasis>0</emphasis>, + <emphasis>1</emphasis>, and <emphasis>multiple</emphasis> results. + We've covered no results in our first test, so now we'll look at a single result. This means that we can + concentrate on how the mechanisms for searching and displaying fit together, without worrying (for the moment) + about things like which collection to use or how to manage scrollbars. Our test needs to setup the single + result to return, check that the result has been passed through to the output component, and check that the + status field is empty, something like this: + </para> - <programlisting> + <programlisting> public void testOneMatchFound() { Searcher searcher = new Searcher(); @@ -359,20 +361,141 @@ ((JLabel)findNamedComponent(searcher, "status")).getText()); }</programlisting> - <para> - Unfortunately, we don't have a <function>addSearchResult()</function> method. We don't yet know where the - results for our <classname>Searcher</classname> come from. Perhaps the directory service isn't ready yet, - or the customer is locked in contract negotiations with its owner. We'll just have to invent something to be - going on with. What we really need is to understand how the <classname>Searcher</classname> will talk to the - directory when it eventually arrives, so we'll introduce an interface <classname>Directory</classname>; we - can pass a directory object through to the <classname>Searcher</classname> when we construct it. For now, - we'll just have the <classname>Directory</classname> return the result when asked, or <varname>null</varname> - if there is none. - </para> - </chapter> + <para> + Unfortunately, we don't have a <function>addSearchResult()</function> method. We don't yet know where the + results for our <classname>Searcher</classname> come from. Perhaps the directory service isn't ready yet, + or the customer is locked in contract negotiations with its owner. We'll have to invent something to be + going on with. Actually, we don't really care yet. What we really need is to understand how the + <classname>Searcher</classname> will talk to the directory when it eventually arrives, so we'll + introduce an interface <classname>Directory</classname>; we can pass a directory object through to + the <classname>Searcher</classname> in the constructor. For now, we'll just have the + <classname>Directory</classname> return a result when asked, or <varname>null</varname> if there is none. + That suggests an interface like the following. + </para> + + <programlisting> +public interface Directory { + String lookFor(String searchString); +}</programlisting> + + <para> + Now we have to create temporary instances of <classname>Directory</classname> when setting up our test cases. + </para> + + <programlisting> +public void testNoMatchesFound() { + Searcher searcher = new Searcher(<emphasis>new Directory() { + public String lookFor(String searchString) { + return null; + } + }</emphasis>); + <emphasis>[...]</emphasis> + +public void testOneMatchFound() { + Searcher searcher = new Searcher(<emphasis>new Directory() { + public String lookFor(String searchString) { + return "One Result"; + } + }</emphasis>); + ((JButton)findNamedComponent(searcher, "search button")).doClick(); + + assertEquals("Should be result", + "One Result", + ((JTextArea)findNamedComponent(searcher, "results")).getText()); + assertEquals("Should be status", + "", + ((JLabel)findNamedComponent(searcher, "status")).getText().trim()); +}</programlisting> + + <para> + The first implementation of <classname>Directory</classname> returns <varname>null</varname>, because that + test represents there being no results. The second implementation returns the string + <constant>"One Result"</constant>, which is what we expect to see loaded into the output component. Let's see + what fails first (I'll fix the missing <classname>JTextArea</classname> first just to speed things up a bit + here). + </para> + + <screen> +There was 1 failure: +1) testOneMatchFound(nostone.tests.SearcherTest)junit.framework.AssertionFailedError: + Should be result expected:<One Result> but was:<> + at nostone.tests.SearcherTest.testOneMatchFound(SearcherTest.java:44) +FAILURES!!!</screen> + &redbar; + + <para> + Of course, we haven't implemented the code to make the search and check the result. Let's do so now and + add the behaviour to the <classname>ActionListener</classname>. + </para> + + <programlisting> +public Searcher(final Directory directory) throws HeadlessException { + super("Searcher"); + + JButton searchButton = new JButton("Search"); + searchButton.setName("search button"); + + final JLabel statusBar = new JLabel(" "); + statusBar.setName("status"); + + final JTextArea results = new JTextArea(); + results.setName("results"); + + searchButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + <emphasis>String result = directory.lookFor(""); + if (null == result) { + statusBar.setText("No entries found"); + } else { + results.setText(result); + } + } + }</emphasis>); + + getContentPane().add(searchButton, BorderLayout.NORTH); + getContentPane().add(results, BorderLayout.CENTER); + getContentPane().add(statusBar, BorderLayout.SOUTH); + pack(); +}</programlisting> + + &greenbar; + + <para>Once again, let's see the application in action:</para> + <screenshot><mediaobject> + <imageobject><imagedata fileref="images/gui_example3.gif" format="gif" /></imageobject> + </mediaobject></screenshot> + + </sect1> <!-- Introducing a Directory --> + + <sect1> + <title>Expecting the search string</title> + <para> + We're missing an important part of the behaviour, there's no way for users to specify what they're looking + for. The <classname>ActionListener</classname> simply passes an empty string to the + <classname>Directory</classname>. Looking again, the requirements say nothing about a user typing in a + search string, everybody just assumed it. Clearly this is a contrived example, but these things do happen in + the Real World. The nice thing about the early feedback from incremental development is that you discover + problems when you still have time to do something about them, rather than after all the time (and money) has + been spent. We confirm our suspicions with our customer that they actually wanted to be able to enter search + strings and go back to work on the tests. + </para> + </sect1> <!-- Expecting the search string --> + + <sect1> + <title>What have we learned?</title> + </sect1> <!-- What have we learned? --> + + </chapter> <!-- Second test --> <appendix id="findNamedComponent" status="todo"> <title>Finding GUI components</title> + </appendix> + + <appendix status="todo"> + <title>To do.</title> + <simplelist type="vert"> + <member>redo GUI images on the same platform.</member> + </simplelist> </appendix> </part> |