From: Steve F. <sm...@us...> - 2002-08-21 23:49:44
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv3836/doc/xdocs Modified Files: a_longer_example.xml Log Message: First test runs, but doesn't test anything. Index: a_longer_example.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/a_longer_example.xml,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- a_longer_example.xml 21 Aug 2002 21:32:16 -0000 1.1 +++ a_longer_example.xml 21 Aug 2002 23:49:40 -0000 1.2 @@ -33,18 +33,142 @@ Our output, and its associated test, is very simple. </subtitle> - <itemizedlist> - <listitem><para> - accept a name and find no result. - </para></listitem> - <listitem><para> - accept a name and return a result from a hard-coded collection. - </para></listitem> - <listitem><para> + <section> + <title>We accept a name and find no result</title> + + <para> + The simplest meaningful behaviour that we can think of is to look up a name in an empty address + book, so we don't need any input to start with and will write out a suitable error message. + In addition, the servlet framework requires us to set a content type for the page before we write + anything into it. To exercise the code in our servlet, we will have to provide a working version of the + environment it expects to run in. In production, the servlet engine will call the servlet + with request and response parameters; the servlet will write back to its client by + calling methods on the response parameter. We'll call the class that implements the new + application <classname>AddressBookServlet</classname>. + </para> + + <para> + Let's write a test — we'll step through this slowly. First, we need a test class. Our + convention for the test class name is to add "Test" to the name of our target class. This implies + that, most of the time, we'll write a test class for each of our production classes; we + extend <classname>TestCase</classname> from the &junit; framework. + </para> + + <programlisting> +public class AddressBookServletTest extends TestCase {</programlisting> + + <para> + Next, we write a test method. The method name describes what we + want to prove, so it's easy to understand the output if a test fails. In + this case, we also have to declare servlet and IO exceptions because + the servlet <methodname>service()</methodname> method that we're about to test + declares them too. If either gets thrown by mistake, the &junit; runtime + will handle it as an error. + </para> + + <programlisting> + public void testNoEntries() throws ServletException, IOException {</programlisting> + + <para> + Somewhere in this test we have to create an address book servlet and + call its service method with request and response objects; we don't have + the real ones, so let's create empty implementations. We're not yet + sure what they do but, if nothing else, we need them to get through + the compiler. We'll look at them in more detail shortly. + </para> + + <programlisting> + HttpServletRequest mockRequest = new NullHttpServletRequest(); + HttpServletResponse mockResponse = new NullHttpServletResponse(); + AddressBookServlet servlet = new AddressBookServlet(); + + servlet.service(mockRequest, mockResponse); + } +}</programlisting> + + <para> + Let's see if this test passes. It doesn't test much, not even what it says in its name, but + we just want to see if the basic infrastructure works — and we haven't seen a green + bar in, oh, five minutes. + </para> + &redbar; + <screen> +There was 1 error: +1) testNoEntries(nostone.addressbook.AddressBookServletTest) +java.lang.NullPointerException + at javax.servlet.http.HttpServlet.service(HttpServlet.java:735) + at nostone.addressbook.AddressBookServletTest.testNoEntries(AddressBookServletTest.java:29) + &elipsis; +FAILURES!!! +Tests run: 1, Failures: 0, Errors: 1</screen> + + <para> + Oops. The request object does not have an HTTP method to return, the servlet is looking for a + <constant>GET</constant>, <constant>POST</constant>, or <constant>PUT</constant>. + The relevant method on <classname>HttpServletRequest</classname> is + <methodname>getMethod()</methodname>. We don't really want to implement a proper servlet request, + so we'll force the issue by overriding our null implementation with a hard-coded value; it turns out + that we also need to return a protocol description. The new request object looks like this. + </para> + + <programlisting> + HttpServletRequest mockRequest = new NullHttpServletRequest() { + public String getMethod() { + return "PUT"; + } + public String getProtocol() { + return "HTTP/1.1"; + } + };</programlisting> + + &greenbar; + + <para> + Now the (incomplete) test passes. All it tells us is that we can call our servlet object without crashing; + it's not much, but it's more than we had five minutes ago. + </para> + + <sidebar> + <title>Creating stub classes</title> + <para> + You may have noticed that we're using <classname>NullHttpServletRequest</classname> and + <classname>NullHttpServletResponse</classname>. These aren't part of the standard Java libraries, + they're stub classes that we've generated ourselves. The interfaces <classname>HttpServerRequest</classname> + and <classname>HttpServerResponse</classname> are too large to reimplement, even if that were a good idea, + so we simply write classes that extend them and let our development tool generate stubs for all the methods. + All the development tools do this, although <application>NetBeans</application> has the nice feature that + you can choose to have all the stubbed methods throw exceptions, which warns you if you haven't + overridden a dummy implemention. The result will be something like: + </para> + <programlisting> +public class NullHttpServletRequest implements HttpServletRequest { + public String getAuthType() { + return null; + } + public Cookie[] getCookies() { + return new Cookie[0]; + } + public long getDateHeader(String s) { + return 0; + } + &elipsis;</programlisting> + + <para> + As you've seen in the first example, now we only have to override the methods that we use in our test. + </para> + </sidebar> + </section> + + <section> + <title>accept a name and return a result from a hard-coded collection.</title> + </section> + + <section> + <title> Retrieve the entries from a file, specified as a servlet property. Values are held in memory. - </para></listitem> - </itemizedlist> + </title> + </section> </chapter> <chapter> |