From: Steve F. <sm...@us...> - 2002-08-03 22:23:12
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv24698/doc/xdocs Modified Files: testing_guis_1.xml htmlbook.css Log Message: Index: testing_guis_1.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/testing_guis_1.xml,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- testing_guis_1.xml 2 Aug 2002 21:56:03 -0000 1.1 +++ testing_guis_1.xml 3 Aug 2002 22:23:10 -0000 1.2 @@ -18,78 +18,75 @@ <chapter> <title>A Search Dialogue</title> - <para> - Our customer has asked us to write our favourite demonstration application, a search dialogue that takes - a search string and shows multiple lines of results. After some discussion, the story breaks down into - the following tasks: - </para> - <procedure> - <step> - <para> - The user clicks the <guibutton>Search</guibutton> button. - There are no matches and the status bar shows <emphasis>No entries found</emphasis>. - </para> - </step> - <step> - <para> - The user clicks the <guibutton>Search</guibutton> button, show one matching line. - </para> - </step> - <step> - <para> - Run a search when the user hits <keysym>Return</keysym> in the search string field. - </para> - </step> - <step> - <para> - The user clicks the <guibutton>Search</guibutton> button, there are no matches and any - previous output is cleared. - </para> - </step> - <step> - <para> - At the start of a search, clear the status bar. Also, set the cursor to the hourglass during the search. - </para> - </step> - <step> - <para> - The user clicks the <guibutton>Search</guibutton> button, there are too many results - to show in the available space, so show scrollbars. - </para> - </step> - <step> - <para> - The user clicks the <guibutton>Search</guibutton> button, there are too many results - to show in the available space, stop when there's a pageful before the scrollbars - appear. - </para> - </step> - <step> - <para> - There are too many results, as with the last task. Add a <guibutton>More</guibutton> - button that will show all the results after the first page has been shown. - </para> - </step> - <step> - <para> - Disable the <guibutton>More</guibutton> button unless there is more than a page ofresults to show. - </para> - </step> - </procedure> - <para> - As always, the trick is to find a way to add little bits of functionality to the application so that - 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> - </chapter> + <section> + <title>Requirements</title> + <para> + Our customer has asked us to write our favourite demonstration application, a search dialogue that takes + a search string and shows multiple lines of results; let's say entries in a phonebook. + After some discussion, the story breaks down into the following tasks: + </para> + <procedure> + <step> + <para> + The user clicks the <guibutton>Search</guibutton> button. + There are no matches and the status bar shows <emphasis>No entries found</emphasis>. + </para> + </step> + <step> + <para> + The user clicks the <guibutton>Search</guibutton> button, show one matching line. + </para> + </step> + <step> + <para> + Run a search when the user hits <keysym>Return</keysym> in the search string field. + </para> + </step> + <step> + <para> + The user clicks the <guibutton>Search</guibutton> button, there are no matches and any + previous output is cleared. + </para> + </step> + <step> + <para> + At the start of a search, clear the status bar. Also, set the cursor to the hourglass during the search. + </para> + </step> + <step> + <para> + The user clicks the <guibutton>Search</guibutton> button, there are too many results + to show in the available space, so show scrollbars. + </para> + </step> + <step> + <para> + The user clicks the <guibutton>Search</guibutton> button, there are too many results + to show in the available space, stop when there's a pageful before the scrollbars + appear. + </para> + </step> + <step> + <para> + There are too many results, as with the last task. Add a <guibutton>More</guibutton> + button that will show all the results after the first page has been shown. + </para> + </step> + <step> + <para> + Disable the <guibutton>More</guibutton> button unless there is more than a page of results to show. + </para> + </step> + </procedure> + <para> + As always, the trick is to find a way to add little bits of functionality to the application so that + 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> - <chapter> - <title>The first test</title> - <sidebar> - <para>A search with no results.</para> - </sidebar> <section> - <title>Basic scaffolding</title> + <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). What is the simplest first test for a graphical user interface that we can implement without taking too @@ -156,7 +153,7 @@ <para> I'm prepared to believe this unit test, but I'd really like to <emphasis>see</emphasis> something—we - also believe in early integration testing. I'll tack on a <function>main</function> method and a call to + also believe in early integration testing. I'll tack on a <function>main()</function> method and a call to <function>pack()</function>so that we can run our magnificent first application. (I've also sneaked in a <classname>WindowListener</classname> to catch the window closing event and shut things down cleanly. It's boilerplate to help me get through this demonstration, if you like you can treat it as an exercise for @@ -190,12 +187,17 @@ <para>Which looks like this:</para> <screenshot><mediaobject> <imageobject><imagedata fileref="images/gui_example1.gif" format="gif" /></imageobject> - <caption><para>The first screen</para></caption> </mediaobject></screenshot> + + <para> + What we've done is lay down some basic scaffolding for the rest of our tests and the beginnings of our new + 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> <section> - <title>Finishing the test</title> + <title>Finishing the first test</title> <para> Now we've assembled some core infrastructure, we can finish off the test with some meaningful behaviour. What exactly does this test have to prove to fulfill our first requirement? That the status bar shows an @@ -210,7 +212,9 @@ "No entries found", ((JLabel)findNamedComponent(searcher, "status")).getText()); }</programlisting> + <para>Of course this fails:</para> + <screen> There was 1 error: 1) testNoMatchesFound(nostone.gui.SearcherTest) @@ -219,23 +223,65 @@ FAILURES!!! Tests run: 1, Failures: 0, Errors: 1</screen> + &redbar; <para> - because we don't have a status bar. I'll add a suitable <classname>javax.swing.JLabel</classname>. Now it fails - for a more interesting reason. + because we don't have a status bar. I'll add a suitable <classname>JLabel</classname> and make it pass. While + we're at it, let's make sure that the status field has the appropriate contents. + </para> + <programlisting> +public Searcher() throws HeadlessException { + super("Searcher"); + + JButton searchButton = new JButton("Search"); + searchButton.setName("search button"); + + final JLabel statusBar = new JLabel("No entries found"); + statusBar.setName("status"); + + getContentPane().add(searchButton, BorderLayout.NORTH); + getContentPane().add(statusBar, BorderLayout.SOUTH); + pack(); +}</programlisting> + &greenbar; + <para> + This now passes, so it's helped us to sort out a couple of our interface components, but there's no behaviour + yet. We can force the issue by extending the test. The status field should start empty, as nothing has happened + yet, let's test its contents before the click. + </para> + + <programlisting> +public void testNoMatchesFound() { + Searcher searcher = new Searcher(); + + assertEquals("Should be initial status", + "", + ((JLabel)findNamedComponent(searcher, "status")).getText().trim()); + + ((JButton)findNamedComponent(searcher, "search button")).doClick(); + assertEquals("Should be status", + "No entries found", + ((JLabel)findNamedComponent(searcher, "status")).getText()); +}</programlisting> + + <para> + Which produces a failure. </para> - <screen> + <screen> There was 1 failure: 1) testNoMatchesFound(nostone.gui.SearcherTest)junit.framework.AssertionFailedError: - Should be status expected:<No entries found> but was:< > + Should be status expected:<> but was: <No entries found> at nostone.gui.SearcherTest.testNoMatchesFound(SearcherTest.java:19) FAILURES!!!</screen> + &redbar; + <para> Now we have to add some behaviour so that the status bar is updated. In Swing, that's done with an <classname>ActionListener</classname>. In this case, the behaviour is simple, just set the contents of the status component. Here's the whole of the reworked constructor. </para> + <programlisting> public Searcher() throws HeadlessException { super("Searcher"); @@ -256,11 +302,12 @@ getContentPane().add(statusBar, BorderLayout.SOUTH); pack(); }</programlisting> - &greenbar; - <para>and here's the evidence that it actually works.</para> + + &greenbar; + + <para>and here's the evidence that it actually works:</para> <screenshot><mediaobject> <imageobject><imagedata fileref="images/gui_example2.gif" format="gif" /></imageobject> - <caption><para>With a status message</para></caption> </mediaobject></screenshot> </section> @@ -269,15 +316,59 @@ <para> User interface testing is messy, but not impossible. Graphical frameworks are made up of code, just like other frameworks and, if they're not completely dysfunctional, it should be possible to exercise that code - in isolation. As always, the skill is in being very clear about what will give you enough confidence to carry - on adding to and changing your code, without letting mistakes through or slowing yourself down with - brittle tests. In this case, we've decided that we don't care about layout, just that various components are - present <emphasis>somewhere</emphasis> in the application. Obviously, the look of the application must be + in isolation. As always, the skill in test-driven development is to be very clear about what will give + you enough confidence to carry on adding to and changing your code, without letting mistakes through or + slowing yourself down with brittle tests. There's no single definition for this, every team tends to develop + its own "house style". + </para> + <para> + In this case, we've decided that layout doesn't matter for our purposes, just that various + components are <emphasis>somewhere</emphasis> in the application. Obviously, the look of the application must be right (or good enough) before it can ship, but layout and behaviour often change at different rates while - a system is in development—they might even be worked on by different people—and you might not want - your implementation test suite to break because someone's reworked a data panel. + 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> + + <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> + + <programlisting> +public void testOneMatchFound() { + Searcher searcher = new Searcher(); + + <emphasis>addSearchResult("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()); +}</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> <appendix id="findNamedComponent" Index: htmlbook.css =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/htmlbook.css,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- htmlbook.css 2 Aug 2002 22:09:45 -0000 1.2 +++ htmlbook.css 3 Aug 2002 22:23:10 -0000 1.3 @@ -5,5 +5,9 @@ h3 { font-family: Arial, Helvetica, sans-serif; font-style: italic; font-weight: bold} p { font-family: Arial, Helvetica, sans-serif} li { font-family: Arial, Helvetica, sans-serif } -pre { margin-left: 5%; } - +.programlisting { margin-left: 5%; } +.screen { margin-left: 5%; } +.sidebar { border: double black 1px; font-size: 80%; padding: 4px; text-align: center; + margin-left: 80%; } +.screenshot { text-align: center; } +.caption { font-size: 80%; font-style: italic; } |