You can subscribe to this list here.
2001 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
(13) |
Aug
(151) |
Sep
(21) |
Oct
(6) |
Nov
(70) |
Dec
(8) |
---|---|---|---|---|---|---|---|---|---|---|---|---|
2002 |
Jan
(47) |
Feb
(66) |
Mar
(23) |
Apr
(115) |
May
(24) |
Jun
(53) |
Jul
(10) |
Aug
(279) |
Sep
(84) |
Oct
(149) |
Nov
(138) |
Dec
(52) |
2003 |
Jan
(22) |
Feb
(20) |
Mar
(29) |
Apr
(106) |
May
(170) |
Jun
(122) |
Jul
(70) |
Aug
(64) |
Sep
(27) |
Oct
(71) |
Nov
(49) |
Dec
(9) |
2004 |
Jan
(7) |
Feb
(38) |
Mar
(3) |
Apr
(9) |
May
(22) |
Jun
(4) |
Jul
(1) |
Aug
(2) |
Sep
(2) |
Oct
|
Nov
(15) |
Dec
(2) |
2005 |
Jan
(1) |
Feb
(1) |
Mar
|
Apr
(1) |
May
(28) |
Jun
(3) |
Jul
(11) |
Aug
(5) |
Sep
(1) |
Oct
(5) |
Nov
(2) |
Dec
(3) |
2006 |
Jan
(8) |
Feb
(3) |
Mar
(8) |
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
|
Dec
|
From: Steve F. <sm...@us...> - 2002-08-22 09:41:57
|
Update of /cvsroot/mockobjects/mockobjects-java/src/jdk/common/com/mockobjects/io In directory usw-pr-cvs1:/tmp/cvs-serv17107/src/jdk/common/com/mockobjects/io Added Files: MockWriter.java Log Message: new class --- NEW FILE: MockWriter.java --- package com.mockobjects.io; import com.mockobjects.ExpectationCounter; import com.mockobjects.ExpectationSegment; import com.mockobjects.Verifiable; import com.mockobjects.util.Verifier; import java.io.IOException; import java.io.Writer; /** * A mock {@link java.io.Writer Writer}. * <h3>Example usage</h3> * <p> * You may use the <code>MockWriter</code> like this: * <pre> * public void testSomething() throws IOException { * MockWriter out = new MockWriter(); * out.setExpectedSegment("some string"); * out.setExpectedFlushCalls(1); * out.setExpectedCloseCalls(1); * * ObjectUnderTest testee = new ObjectUnderTest(out); * out.verify(); * * // If we get here, the mock's flush() and close() methods were * // called exactly once each (order cannot be determined) and * // the write() method was called with the string "some string" in it. * }</pre> * </p> * @author Francois Beausoleil, fb...@us... */ public class MockWriter extends Writer implements Verifiable { private ExpectationSegment mySegment = new ExpectationSegment("String segment"); private ExpectationCounter myFlushCalls = new ExpectationCounter("flush calls"); private ExpectationCounter myCloseCalls = new ExpectationCounter("close calls"); private boolean writeShouldThrowException = false; /** * Instantiates a new mock writer which will act as a data sink. * Once instantiated, mocks of this class do not expect anything special. * Once the object is instantiated, you should set your expectations using * the provided methods. */ public MockWriter() { } /** * Sets the mocks behavior when writing. * When the {@link #write(char[],int,int) write(char[], int, int)} method * is called, if this method was called with <code>true</code>, the mock * will throw an {@link java.io.IOException IOException} instead of * asserting a string segment. * @param state Set to <code>true</code> if you want the mock to throw an * exception, <code>false</code> otherwise. */ public void setWriteShouldThrowException(boolean state) { writeShouldThrowException = state; } /** * Sets the expected number of times that the {@link #flush() flush()} * method will be called. * @see #flush() */ public void setExpectedFlushCalls(int calls) { myFlushCalls.setExpected(calls); } /** * Sets the expected number of times that the {@link #close() close()} * method will be called. * @see #close() */ public void setExpectedCloseCalls(int calls) { myCloseCalls.setExpected(calls); } /** * Sets the value of the expected string segment. * When the {@link #write(char[], int, int) write(char[], int, int)} method * is called, a string is instantiated with the passed array and compared * to the <code>aString</code> parameter of this method. If the two strings * differ, an {@link junit.framework.AssertionFailedError} will be thrown. * @see com.mockobjects.ExpectationSegment * @see #write(char[], int, int) */ public void setExpectedSegment(String aString) { mySegment.setExpected(aString); } /** * Either throws an exception or asserts a string segment for equality. * @see com.mockobjects.ExpectationSegment * @see #setWriteShouldThrowException(boolean) */ public void write(char cbuf[], int off, int len) throws IOException { if (writeShouldThrowException) { throw new IOException("Exception, as requested"); } mySegment.setActual(new String(cbuf, off, len)); } /** * Increments the flush counter and asserts that this method was not * called too many times. * @see #setExpectedFlushCalls(int) */ public void flush() throws IOException { myFlushCalls.inc(); } /** * Increments the close counter and asserts that this method was not * called too many times. * @see #setExpectedCloseCalls(int) */ public void close() { myCloseCalls.inc(); } public void verify() { mySegment.verify(); myFlushCalls.verify(); myCloseCalls.verify(); } } |
From: Steve F. <sm...@us...> - 2002-08-22 09:33:48
|
Update of /cvsroot/mockobjects/mockobjects-java/src/jdk/common/com/mockobjects/io In directory usw-pr-cvs1:/tmp/cvs-serv14556/src/jdk/common/com/mockobjects/io Modified Files: MockPrintWriter.java Log Message: removed extra colon from javadoc tag tidied up imports Index: MockPrintWriter.java =================================================================== RCS file: /cvsroot/mockobjects/mockobjects-java/src/jdk/common/com/mockobjects/io/MockPrintWriter.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- MockPrintWriter.java 13 Apr 2002 15:08:23 -0000 1.1 +++ MockPrintWriter.java 22 Aug 2002 09:33:44 -0000 1.2 @@ -1,12 +1,16 @@ package com.mockobjects.io; -import com.mockobjects.*; -import junit.framework.Assert; -import java.io.*; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; + +import com.mockobjects.ExpectationCounter; +import com.mockobjects.ExpectationSegment; +import com.mockobjects.Verifiable; /** - * @author: st...@m3... + * @author st...@m3... */ public class MockPrintWriter extends PrintWriter implements Verifiable { |
From: Francois B. <fbe...@ft...> - 2002-08-22 02:08:13
|
Hi, I feel quite the fool now... Seems I should write test cases for mocks too. Anyway, the previously submitted MockWriter will throw a StackOverflowError when verifying. Below is code that really works. Sorry again for that. Have a nice day ! Francois Beausoleil ==================================================================package com.mockobjects.io; import com.mockobjects.ExpectationCounter; import com.mockobjects.ExpectationSegment; import com.mockobjects.Verifiable; import com.mockobjects.util.Verifier; import java.io.IOException; import java.io.Writer; /** * A mock {@link java.io.Writer Writer}. * <h3>Example usage</h3> * <p> * You may use the <code>MockWriter</code> like this: * <pre> * public void testSomething() throws IOException { * MockWriter out = new MockWriter(); * out.setExpectedSegment("some string"); * out.setExpectedFlushCalls(1); * out.setExpectedCloseCalls(1); * * ObjectUnderTest testee = new ObjectUnderTest(out); * out.verify(); * * // If we get here, the mock's flush() and close() methods were * // called exactly once each (order cannot be determined) and * // the write() method was called with the string "some string" in it. * }</pre> * </p> * @author Francois Beausoleil, fb...@us... */ public class MockWriter extends Writer implements Verifiable { private ExpectationSegment mySegment = new ExpectationSegment("String segment"); private ExpectationCounter myFlushCalls = new ExpectationCounter("flush calls"); private ExpectationCounter myCloseCalls = new ExpectationCounter("close calls"); private boolean myShouldWriteThrowException = false; /** * Instantiates a new mock writer which will act as a data sink. * Once instantiated, mocks of this class do not expect anything special. * Once the object is instantiated, you should set your expectations using * the provided methods. */ public MockWriter() { } /** * Sets the mocks behavior when writing. * When the {@link #write(char[],int,int) write(char[], int, int)} method * is called, if this method was called with <code>true</code>, the mock * will throw an {@link java.io.IOException IOException} instead of * asserting a string segment. * @param state Set to <code>true</code> if you want the mock to throw an * exception, <code>false</code> otherwise. */ public void setWriteShouldThrowException(boolean state) { myShouldWriteThrowException = state; } /** * Sets the expected number of times that the {@link #flush() flush()} * method will be called. * @see #flush() */ public void setExpectedFlushCalls(int calls) { myFlushCalls.setExpected(calls); } /** * Sets the expected number of times that the {@link #close() close()} * method will be called. * @see #close() */ public void setExpectedCloseCalls(int calls) { myCloseCalls.setExpected(calls); } /** * Sets the value of the expected string segment. * When the {@link #write(char[], int, int) write(char[], int, int)} method * is called, a string is instantiated with the passed array and compared * to the <code>aString</code> parameter of this method. If the two strings * differ, an {@link junit.framework.AssertionFailedError} will be thrown. * @see com.mockobjects.ExpectationSegment * @see #write(char[], int, int) */ public void setExpectedSegment(String aString) { mySegment.setExpected(aString); } /** * Either throws an exception or asserts a string segment for equality. * @see com.mockobjects.ExpectationSegment * @see #setWriteShouldThrowException(boolean) */ public void write(char cbuf[], int off, int len) throws IOException { if (myShouldWriteThrowException) { throw new IOException("Exception, as requested"); } mySegment.setActual(new String(cbuf, off, len)); } /** * Increments the flush counter and asserts that this method was not * called too many times. * @see #setExpectedFlushCalls(int) */ public void flush() throws IOException { myFlushCalls.inc(); } /** * Increments the close counter and asserts that this method was not * called too many times. * @see #setExpectedCloseCalls(int) */ public void close() { myCloseCalls.inc(); } public void verify() { mySegment.verify(); myFlushCalls.verify(); myCloseCalls.verify(); } } ================================================================== |
From: Francois B. <fbe...@ft...> - 2002-08-22 01:58:54
|
Hi ! I just noticed about that. Sorry for any inconvenience. Bye ! Francois |
From: <fra...@ya...> - 2002-08-22 01:57:28
|
Hello all ! I would like to submit the following for inclusion in the CVS tree. Thanks ! Francois Beausoleil ========================================================== package com.mockobjects.io; import com.mockobjects.ExpectationCounter; import com.mockobjects.ExpectationSegment; import com.mockobjects.Verifiable; import com.mockobjects.util.Verifier; import java.io.IOException; import java.io.Writer; /** * A mock {@link java.io.Writer Writer}. * <h3>Example usage</h3> * <p> * You may use the <code>MockWriter</code> like this: * <pre> * public void testSomething() throws IOException { * MockWriter out = new MockWriter(); * out.setExpectedSegment("some string"); * out.setExpectedFlushCalls(1); * out.setExpectedCloseCalls(1); * * ObjectUnderTest testee = new ObjectUnderTest(out); * out.verify(); * * // If we get here, the mock's flush() and close() methods were * // called exactly once each (order cannot be determined) and * // the write() method was called with the string "some string" in it. * }</pre> * </p> * @author Francois Beausoleil, fb...@us... */ public class MockWriter extends Writer implements Verifiable { private ExpectationSegment mySegment = new ExpectationSegment("String segment"); private ExpectationCounter myCloseCalls = new ExpectationCounter("close calls"); private ExpectationCounter myFlushCalls = new ExpectationCounter("flush calls"); private boolean myShouldWriteThrowException = false; /** * Instantiates a new mock writer which will act as a data sink. * Once instantiated, mocks of this class do not expect anything special. * Once the object is instantiated, you should set your expectations using * the provided methods. */ public MockWriter() { } /** * Sets the mocks behavior when writing. * When the {@link #write(char[],int,int) write(char[], int, int)} method * is called, if this method was called with <code>true</code>, the mock * will throw an {@link java.io.IOException IOException} instead of * asserting a string segment. * @param state Set to <code>true</code> if you want the mock to throw an * exception, <code>false</code> otherwise. */ public void setWriteShouldThrowException(boolean state) { myShouldWriteThrowException = state; } /** * Sets the expected number of times that the {@link #flush() flush()} * method will be called. * @see #flush() */ public void setExpectedFlushCalls(int calls) { myFlushCalls.setExpected(calls); } /** * Sets the expected number of times that the {@link #close() close()} * method will be called. * @see #close() */ public void setExpectedCloseCalls(int calls) { myCloseCalls.setExpected(calls); } /** * Sets the value of the expected string segment. * When the {@link #write(char[], int, int) write(char[], int, int)} method * is called, a string is instantiated with the passed array and compared * to the <code>aString</code> parameter of this method. If the two strings * differ, an {@link junit.framework.AssertionFailedError} will be thrown. * @see com.mockobjects.ExpectationSegment * @see #write(char[], int, int) */ public void setExpectedSegment(String aString) { mySegment.setExpected(aString); } /** * Either throws an exception or asserts a string segment for equality. * @see com.mockobjects.ExpectationSegment * @see #setWriteShouldThrowException(boolean) */ public void write(char cbuf[], int off, int len) throws IOException { if (myShouldWriteThrowException) { throw new IOException("Exception, as requested"); } mySegment.setActual(new String(cbuf, off, len)); } /** * Increments the flush counter and asserts that this method was not * called too many times. * @see #setExpectedFlushCalls(int) */ public void flush() throws IOException { myFlushCalls.inc(); } /** * Increments the close counter and asserts that this method was not * called too many times. * @see #setExpectedCloseCalls(int) */ public void close() { myCloseCalls.inc(); } public void verify() { Verifier.verifyObject(this); } } ========================================================== ===== Francois Beausoleil Java Gui Builder - http://jgb.sourceforge.net/ __________________________________________________________ Lèche-vitrine ou lèche-écran ? magasinage.yahoo.ca |
From: <fra...@ya...> - 2002-08-22 01:57:02
|
Hello all ! I found a tiny problem with com.mockobjects.io.MockPrintWriter. I have included a diff to correct this problem. The problem is a colon on the author tag, which should not be there. Javadoc chokes on it. Have a nice day ! Francois Beausoleil Index: src/jdk/common/com/mockobjects/io/MockPrintWriter.java =================================================================== RCS file: /cvsroot/mockobjects/mockobjects-java/src/jdk/common/com/mockobjects/io/MockPrintWriter.java,v retrieving revision 1.1 diff -u -r1.1 MockPrintWriter.java --- src/jdk/common/com/mockobjects/io/MockPrintWriter.java 13 Apr 2002 15:08:23 -0000 1.1 +++ src/jdk/common/com/mockobjects/io/MockPrintWriter.java 22 Aug 2002 01:48:25 -0000 @@ -6,7 +6,7 @@ /** - * @author: st...@m3... + * @author st...@m3... */ public class MockPrintWriter extends PrintWriter implements Verifiable { ===== Francois Beausoleil Java Gui Builder - http://jgb.sourceforge.net/ __________________________________________________________ Lèche-vitrine ou lèche-écran ? magasinage.yahoo.ca |
From: Francois B. <fbe...@ft...> - 2002-08-22 01:55:02
|
Hello all ! I found a tiny problem with com.mockobjects.io.MockPrintWriter. I have included a diff to correct this problem. The problem is a colon on the author tag, which should not be there. Javadoc chokes on it. Have a nice day ! Francois Beausoleil Index: src/jdk/common/com/mockobjects/io/MockPrintWriter.java =================================================================== RCS file: /cvsroot/mockobjects/mockobjects-java/src/jdk/common/com/mockobjects/io/MockPrintWriter.java,v retrieving revision 1.1 diff -u -r1.1 MockPrintWriter.java --- src/jdk/common/com/mockobjects/io/MockPrintWriter.java 13 Apr 2002 15:08:23 -0000 1.1 +++ src/jdk/common/com/mockobjects/io/MockPrintWriter.java 22 Aug 2002 01:48:25 -0000 @@ -6,7 +6,7 @@ /** - * @author: st...@m3... + * @author st...@m3... */ public class MockPrintWriter extends PrintWriter implements Verifiable { |
From: Francois B. <fbe...@ft...> - 2002-08-22 01:52:58
|
Hello all ! I would like to submit the following for inclusion in the CVS tree. Thanks ! Francois Beausoleil ========================================================== package com.mockobjects.io; import com.mockobjects.ExpectationCounter; import com.mockobjects.ExpectationSegment; import com.mockobjects.Verifiable; import com.mockobjects.util.Verifier; import java.io.IOException; import java.io.Writer; /** * A mock {@link java.io.Writer Writer}. * <h3>Example usage</h3> * <p> * You may use the <code>MockWriter</code> like this: * <pre> * public void testSomething() throws IOException { * MockWriter out = new MockWriter(); * out.setExpectedSegment("some string"); * out.setExpectedFlushCalls(1); * out.setExpectedCloseCalls(1); * * ObjectUnderTest testee = new ObjectUnderTest(out); * out.verify(); * * // If we get here, the mock's flush() and close() methods were * // called exactly once each (order cannot be determined) and * // the write() method was called with the string "some string" in it. * }</pre> * </p> * @author Francois Beausoleil, fb...@us... */ public class MockWriter extends Writer implements Verifiable { private ExpectationSegment mySegment = new ExpectationSegment("String segment"); private ExpectationCounter myCloseCalls = new ExpectationCounter("close calls"); private ExpectationCounter myFlushCalls = new ExpectationCounter("flush calls"); private boolean myShouldWriteThrowException = false; /** * Instantiates a new mock writer which will act as a data sink. * Once instantiated, mocks of this class do not expect anything special. * Once the object is instantiated, you should set your expectations using * the provided methods. */ public MockWriter() { } /** * Sets the mocks behavior when writing. * When the {@link #write(char[],int,int) write(char[], int, int)} method * is called, if this method was called with <code>true</code>, the mock * will throw an {@link java.io.IOException IOException} instead of * asserting a string segment. * @param state Set to <code>true</code> if you want the mock to throw an * exception, <code>false</code> otherwise. */ public void setWriteShouldThrowException(boolean state) { myShouldWriteThrowException = state; } /** * Sets the expected number of times that the {@link #flush() flush()} * method will be called. * @see #flush() */ public void setExpectedFlushCalls(int calls) { myFlushCalls.setExpected(calls); } /** * Sets the expected number of times that the {@link #close() close()} * method will be called. * @see #close() */ public void setExpectedCloseCalls(int calls) { myCloseCalls.setExpected(calls); } /** * Sets the value of the expected string segment. * When the {@link #write(char[], int, int) write(char[], int, int)} method * is called, a string is instantiated with the passed array and compared * to the <code>aString</code> parameter of this method. If the two strings * differ, an {@link junit.framework.AssertionFailedError} will be thrown. * @see com.mockobjects.ExpectationSegment * @see #write(char[], int, int) */ public void setExpectedSegment(String aString) { mySegment.setExpected(aString); } /** * Either throws an exception or asserts a string segment for equality. * @see com.mockobjects.ExpectationSegment * @see #setWriteShouldThrowException(boolean) */ public void write(char cbuf[], int off, int len) throws IOException { if (myShouldWriteThrowException) { throw new IOException("Exception, as requested"); } mySegment.setActual(new String(cbuf, off, len)); } /** * Increments the flush counter and asserts that this method was not * called too many times. * @see #setExpectedFlushCalls(int) */ public void flush() throws IOException { myFlushCalls.inc(); } /** * Increments the close counter and asserts that this method was not * called too many times. * @see #setExpectedCloseCalls(int) */ public void close() { myCloseCalls.inc(); } public void verify() { Verifier.verifyObject(this); } } ========================================================== |
From: Francois B. <fbe...@ft...> - 2002-08-22 01:51:25
|
Hello all ! I found a tiny problem with com.mockobjects.io.MockPrintWriter. I have included a diff to correct this problem. The problem is a colon on the author tag, which should not be there. Javadoc chokes on it. Have a nice day ! Francois Beausoleil Index: src/jdk/common/com/mockobjects/io/MockPrintWriter.java =================================================================== RCS file: /cvsroot/mockobjects/mockobjects-java/src/jdk/common/com/mockobjects/io/MockPrintWriter.java,v retrieving revision 1.1 diff -u -r1.1 MockPrintWriter.java --- src/jdk/common/com/mockobjects/io/MockPrintWriter.java 13 Apr 2002 15:08:23 -0000 1.1 +++ src/jdk/common/com/mockobjects/io/MockPrintWriter.java 22 Aug 2002 01:48:25 -0000 @@ -6,7 +6,7 @@ /** - * @author: st...@m3... + * @author st...@m3... */ public class MockPrintWriter extends PrintWriter implements Verifiable { |
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> |
From: Steve F. <sm...@us...> - 2002-08-21 23:49:43
|
Update of /cvsroot/mockobjects/no-stone-unturned/src/nostone/addressbook In directory usw-pr-cvs1:/tmp/cvs-serv3836/src/nostone/addressbook Modified Files: AddressBookServletTest.java Log Message: First test runs, but doesn't test anything. Index: AddressBookServletTest.java =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/src/nostone/addressbook/AddressBookServletTest.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- AddressBookServletTest.java 21 Aug 2002 21:58:34 -0000 1.1 +++ AddressBookServletTest.java 21 Aug 2002 23:49:39 -0000 1.2 @@ -4,7 +4,7 @@ import javax.servlet.http.*; import javax.servlet.ServletException; -import java.io.IOException; +import java.io.*; public class AddressBookServletTest extends TestCase { public AddressBookServletTest(String name) { @@ -12,11 +12,29 @@ } public void testNoEntries() throws ServletException, IOException { - HttpServletRequest mockRequest = new NullHttpServletRequest(); - HttpServletResponse mockResponse = new NullHttpServletResponse(); + final StringWriter page = new StringWriter(); - AddressBookServlet servlet = new AddressBookServlet(); + HttpServletRequest mockRequest = new NullHttpServletRequest() { + public String getMethod() { + return "PUT"; + } + public String getProtocol() { + return "HTTP/1.1"; + } + }; + HttpServletResponse mockResponse = new NullHttpServletResponse() /*{ + public void setContentType(String contentType) { + assertEquals("Should be content type", "text/plain", contentType); + } + + public PrintWriter getWriter() throws IOException { + return new PrintWriter(page); + } + }*/; + AddressBookServlet servlet = new AddressBookServlet(); servlet.service(mockRequest, mockResponse); + + // assertEquals("Response page", "No address found", page.toString()); } } |
Update of /cvsroot/mockobjects/no-stone-unturned/src/nostone/addressbook In directory usw-pr-cvs1:/tmp/cvs-serv7896/src/nostone/addressbook Added Files: AddressBookServlet.java NullHttpServletRequest.java AddressBookServletTest.java NullHttpServletResponse.java Log Message: Started first test, Created Null request and response. --- NEW FILE: AddressBookServlet.java --- package nostone.addressbook; import javax.servlet.http.HttpServlet; public class AddressBookServlet extends HttpServlet { public AddressBookServlet() { } } --- NEW FILE: NullHttpServletRequest.java --- package nostone.addressbook; import javax.servlet.http.*; import javax.servlet.ServletInputStream; import javax.servlet.RequestDispatcher; import java.util.*; import java.security.Principal; import java.io.*; public class NullHttpServletRequest implements HttpServletRequest { public String getAuthType() { return null; } public Cookie[] getCookies() { return new Cookie[0]; } public long getDateHeader(String s) { return 0; } public String getHeader(String s) { return null; } public Enumeration getHeaders(String s) { return null; } public Enumeration getHeaderNames() { return null; } public int getIntHeader(String s) { return 0; } public String getMethod() { return null; } public String getPathInfo() { return null; } public String getPathTranslated() { return null; } public String getContextPath() { return null; } public String getQueryString() { return null; } public String getRemoteUser() { return null; } public boolean isUserInRole(String s) { return false; } public Principal getUserPrincipal() { return null; } public String getRequestedSessionId() { return null; } public String getRequestURI() { return null; } public StringBuffer getRequestURL() { return null; } public String getServletPath() { return null; } public HttpSession getSession(boolean b) { return null; } public HttpSession getSession() { return null; } public boolean isRequestedSessionIdValid() { return false; } public boolean isRequestedSessionIdFromCookie() { return false; } public boolean isRequestedSessionIdFromURL() { return false; } public boolean isRequestedSessionIdFromUrl() { return false; } public Object getAttribute(String s) { return null; } public Enumeration getAttributeNames() { return null; } public String getCharacterEncoding() { return null; } public void setCharacterEncoding(String s) throws UnsupportedEncodingException { } public int getContentLength() { return 0; } public String getContentType() { return null; } public ServletInputStream getInputStream() throws IOException { return null; } public String getParameter(String s) { return null; } public Enumeration getParameterNames() { return null; } public String[] getParameterValues(String s) { return new String[0]; } public Map getParameterMap() { return null; } public String getProtocol() { return null; } public String getScheme() { return null; } public String getServerName() { return null; } public int getServerPort() { return 0; } public BufferedReader getReader() throws IOException { return null; } public String getRemoteAddr() { return null; } public String getRemoteHost() { return null; } public void setAttribute(String s, Object o) { } public void removeAttribute(String s) { } public Locale getLocale() { return null; } public Enumeration getLocales() { return null; } public boolean isSecure() { return false; } public RequestDispatcher getRequestDispatcher(String s) { return null; } public String getRealPath(String s) { return null; } } --- NEW FILE: AddressBookServletTest.java --- package nostone.addressbook; import junit.framework.TestCase; import javax.servlet.http.*; import javax.servlet.ServletException; import java.io.IOException; public class AddressBookServletTest extends TestCase { public AddressBookServletTest(String name) { super(name); } public void testNoEntries() throws ServletException, IOException { HttpServletRequest mockRequest = new NullHttpServletRequest(); HttpServletResponse mockResponse = new NullHttpServletResponse(); AddressBookServlet servlet = new AddressBookServlet(); servlet.service(mockRequest, mockResponse); } } --- NEW FILE: NullHttpServletResponse.java --- package nostone.addressbook; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.Cookie; import javax.servlet.ServletOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.util.Locale; public class NullHttpServletResponse implements HttpServletResponse { public void addCookie(Cookie cookie) { } public boolean containsHeader(String s) { return false; } public String encodeURL(String s) { return null; } public String encodeRedirectURL(String s) { return null; } public String encodeUrl(String s) { return null; } public String encodeRedirectUrl(String s) { return null; } public void sendError(int i, String s) throws IOException { } public void sendError(int i) throws IOException { } public void sendRedirect(String s) throws IOException { } public void setDateHeader(String s, long l) { } public void addDateHeader(String s, long l) { } public void setHeader(String s, String s1) { } public void addHeader(String s, String s1) { } public void setIntHeader(String s, int i) { } public void addIntHeader(String s, int i) { } public void setStatus(int i) { } public void setStatus(int i, String s) { } public String getCharacterEncoding() { return null; } public ServletOutputStream getOutputStream() throws IOException { return null; } public PrintWriter getWriter() throws IOException { return null; } public void setContentLength(int i) { } public void setContentType(String s) { } public void setBufferSize(int i) { } public int getBufferSize() { return 0; } public void flushBuffer() throws IOException { } public void resetBuffer() { } public boolean isCommitted() { return false; } public void reset() { } public void setLocale(Locale locale) { } public Locale getLocale() { return null; } } |
From: Steve F. <sm...@us...> - 2002-08-21 21:57:49
|
Update of /cvsroot/mockobjects/no-stone-unturned/src/nostone/addressbook In directory usw-pr-cvs1:/tmp/cvs-serv7629/src/nostone/addressbook Log Message: Directory /cvsroot/mockobjects/no-stone-unturned/src/nostone/addressbook added to the repository |
From: Steve F. <sm...@us...> - 2002-08-21 21:32:21
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv30180/doc/xdocs Modified Files: htmlbook.css Added Files: a_longer_example.xml Log Message: started rework of main example. Wrote a sketch for the steps --- NEW FILE: a_longer_example.xml --- <part status="todo"> <title>Iteration 1</title> <preface> <title>Requirements</title> <para> Our customers have commissioned us to write a web-based address book. Anyone can add and remove themselves from the list, but only the list owner can see the whole list. If we start by assuming that there's just one list, we have the following tasks: </para> <itemizedlist> <listitem><para> anyone can see all the entries in the book. The book is retrieved from a file. </para></listitem> <listitem><para> anyone can search the entries in the book. </para></listitem> <listitem><para> anyone can add their name and email address to the book. </para></listitem> <listitem><para> anyone can remove an entry from the book. </para></listitem> </itemizedlist> </preface> <chapter> <title>Search for an entry in the book</title> <subtitle> In which we stub out the servlet environment and then mock up access to the file system. 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> Retrieve the entries from a file, specified as a servlet property. Values are held in memory. </para></listitem> </itemizedlist> </chapter> <chapter> <title>List all the entries in the book</title> <subtitle> In which we contemplate more deeply the rendering of our entries that we may display the entire address book at once. </subtitle> <itemizedlist> <listitem><para> show an empty book. </para></listitem> <listitem><para> show all the entries </para></listitem> <listitem><para> Sort the results alphabetically </para></listitem> </itemizedlist> </chapter> <chapter> <title>Add a name and address</title> <subtitle> In which we allow the common people to add their own name and address to the book which introduces us to failures and catastrophes that might occur during a transaction. </subtitle> <itemizedlist> <listitem><para> accept a name and address, add to store, and rewrite the file. </para></listitem> <listitem><para> lock the file while it's being written. </para></listitem> </itemizedlist> </chapter> </part> <part status="todo"> <title>Iteration 2</title> <chapter> <title>Port to database</title> <subtitle> Show how to test code that uses heavyweight resources, by mocking their implementation. Refactor (before or after) a persistence layer. </subtitle> </chapter> <chapter> <title>Remove a name</title> <subtitle> Different queries, different failure modes. </subtitle> </chapter> <chapter> <title>Allow multiple entries for a name.</title> <subtitle> In which our customer changes his mind. </subtitle> <para> What about the previous task? Do we remove all entries for a name or ask to choose? Go back to the customer. </para> </chapter> </part> <part status="todo"> <title>Iteration 3</title> <subtitle> Our customer decides to stay at home more and needs a more responsive user interface. </subtitle> <chapter> <title>Search a name</title> </chapter> <chapter> <title>Remove a name</title> <para>Pick entry as well?</para> </chapter> <chapter> <title>List all names</title> <para> Click on name to see its entries. </para> </chapter> </part> Index: htmlbook.css =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/htmlbook.css,v retrieving revision 1.12 retrieving revision 1.13 diff -u -r1.12 -r1.13 --- htmlbook.css 21 Aug 2002 19:16:43 -0000 1.12 +++ htmlbook.css 21 Aug 2002 21:32:16 -0000 1.13 @@ -5,6 +5,8 @@ H1 { font-family: Arial, Helvetica, sans-serif; margin-top: 5%; } H2 { margin-top: 3%; font-style: italic;} +H3.subtitle { font-weight: normal; } + DIV.chapter H2 { font-family: Arial, Helvetica, sans-serif; font-weight: normal; font-size: 200%; } DIV.preface H2 { font-family: "Century Schoolbook", Times, serif; |
From: Steve F. <sm...@us...> - 2002-08-21 19:59:16
|
Update of /cvsroot/mockobjects/no-stone-unturned In directory usw-pr-cvs1:/tmp/cvs-serv28335 Modified Files: .project Log Message: converted to java project Index: .project =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/.project,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- .project 3 Aug 2002 22:23:51 -0000 1.2 +++ .project 21 Aug 2002 19:59:13 -0000 1.3 @@ -3,6 +3,8 @@ <name>no-stone-unturned</name> <comment></comment> <projects> + <project>junit</project> + <project>mockobjects-java</project> </projects> <buildSpec> <buildCommand> |
From: Steve F. <sm...@us...> - 2002-08-21 19:58:56
|
Update of /cvsroot/mockobjects/no-stone-unturned/src/nostone/robot In directory usw-pr-cvs1:/tmp/cvs-serv28176/src/nostone/robot Removed Files: Motor.java Position.java Robot.java RobotTest.java MoveRequest.java Log Message: removed robot example --- Motor.java DELETED --- --- Position.java DELETED --- --- Robot.java DELETED --- --- RobotTest.java DELETED --- --- MoveRequest.java DELETED --- |
From: Steve F. <sm...@us...> - 2002-08-21 19:58:32
|
Update of /cvsroot/mockobjects/no-stone-unturned/src/nostone/junitrunner In directory usw-pr-cvs1:/tmp/cvs-serv27977/src/nostone/junitrunner Removed Files: Tasks.java SwingRunner.java SwingRunnerTest.java CounterPanel.java TestHelper.java Log Message: removed junitrunner --- Tasks.java DELETED --- --- SwingRunner.java DELETED --- --- SwingRunnerTest.java DELETED --- --- CounterPanel.java DELETED --- --- TestHelper.java DELETED --- |
From: Steve F. <sm...@us...> - 2002-08-21 19:58:09
|
Update of /cvsroot/mockobjects/no-stone-unturned/src/nostone/tests In directory usw-pr-cvs1:/tmp/cvs-serv27828/src/nostone/tests Removed Files: TestHelper.java SearcherTest.java Log Message: moved tests to gui package --- TestHelper.java DELETED --- --- SearcherTest.java DELETED --- |
From: Steve F. <sm...@us...> - 2002-08-21 19:58:09
|
Update of /cvsroot/mockobjects/no-stone-unturned/src/nostone/gui In directory usw-pr-cvs1:/tmp/cvs-serv27828/src/nostone/gui Added Files: SearcherTest.java TestHelper.java Log Message: moved tests to gui package --- NEW FILE: SearcherTest.java --- package nostone.gui; import junit.framework.TestCase; import javax.swing.*; import java.awt.*; import com.mockobjects.ExpectationValue; public class SearcherTest extends TestCase { public SearcherTest(String name) { super(name); } public void testNoMatchesFound() { Searcher searcher = new Searcher(new Directory() { public String searchFor(String searchString) { return null; } }); assertEquals("Should be 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()); } public void testOneMatchFound() { final ExpectationValue searchString = new ExpectationValue("search string"); Searcher searcher = new Searcher(new Directory() { public String searchFor(String aSearchString) { searchString.setActual(aSearchString); return "One Result"; } }); ((JTextField)findNamedComponent(searcher, "search criterion")).setText("Search String"); searchString.setExpected("Search String"); ((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()); } private Component findNamedComponent(final Container container, final String name) { return new TestHelper().depthFirst(container, new TestHelper.ComponentVisitor() { public Component apply(Component aComponent) { return name.equals(aComponent.getName()) ? aComponent : null; } }); } } --- NEW FILE: TestHelper.java --- package nostone.gui; import java.awt.*; public class TestHelper { public Component depthFirst(Container container, ComponentVisitor visitor) { Component[] components = container.getComponents(); for (int i = 0; i < components.length; i++) { Component component = components[i]; Component applied = visitor.apply(component); if (null != applied) { return applied; } if (component instanceof Container) { applied = depthFirst((Container) component, visitor); if (null != applied) { return applied; } } } return null; } public interface ComponentVisitor { public Component apply(Component aComponent); } } |
From: Steve F. <sm...@us...> - 2002-08-21 19:57:24
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv27356/doc/xdocs Modified Files: how_mocks_happened.xml Log Message: added more notes Index: how_mocks_happened.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/how_mocks_happened.xml,v retrieving revision 1.12 retrieving revision 1.13 diff -u -r1.12 -r1.13 --- how_mocks_happened.xml 21 Aug 2002 19:16:43 -0000 1.12 +++ how_mocks_happened.xml 21 Aug 2002 19:57:21 -0000 1.13 @@ -730,7 +730,8 @@ design decision and, again, we drove it from the process of writing a test. </para> <para> - We've added a very simple + Design by composition, top-down with polymorphism, do the protocols first, shows the fault lines but + it's more than divide and conquer. </para> <para> |
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv32157/doc/xdocs Modified Files: htmlbook.xsl doc-book.xml testing_guis_1.xml how_mocks_happened.xml htmlbook.css Added Files: part_servlets.xml resources.xml Log Message: bits of tidy up and formatting --- NEW FILE: part_servlets.xml --- <part status="draft"> <title>A longer example</title> <partintro> <para> So far, I've covered some basic ideas and attempted to explain why &TDD; is so useful. Now it's time to follow through with a longer example. I've picked a Java servlet, because that's where we initially worked out these ideas <footnote> <para> One of the key moments in the develoment of our unit testing skills was when Oli Bye, while programming on a train, got fed up with trying to test against a live Web server and generated stub implementations of the Java servlet interfaces using <productname>VisualAge for Java</productname>. That's when we realized what we could do if we tested code outside its target environment </para> </footnote>, and because they provide a nice example framework with reasonably clean interfaces. </para> </partintro> &chp_servlets_1; &chp_servlets_2; &chp_servlets_3; </part> --- NEW FILE: resources.xml --- <appendix status="todo" id="appendix_resources"> <title>Resources</title> &TODO; <para>Testing frameworks - Graphical</para> </appendix> Index: htmlbook.xsl =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/htmlbook.xsl,v retrieving revision 1.9 retrieving revision 1.10 diff -u -r1.9 -r1.10 --- htmlbook.xsl 17 Aug 2002 13:20:07 -0000 1.9 +++ htmlbook.xsl 21 Aug 2002 19:16:43 -0000 1.10 @@ -15,8 +15,10 @@ <xsl:param name="callout.defaultcolumn" select="'80'"/> <xsl:param name="callout.graphics.path" select="'images/style/callouts/'"/> <xsl:param name="toc.section.depth">1</xsl:param> + <xsl:param name="emphasis.propagates.style" select="1"/> - <xsl:template name="inline.gui"> + <xsl:template name="inline.span"> + <xsl:param name="spanclass" select="none"/> <xsl:param name="content"> <xsl:call-template name="anchor"/> <xsl:call-template name="simple.xlink"> @@ -25,27 +27,34 @@ </xsl:with-param> </xsl:call-template> </xsl:param> - <span class="guibutton"><xsl:copy-of select="$content"/></span> + <span class="{$spanclass}"><xsl:copy-of select="$content"/></span> </xsl:template> - <xsl:template match="guibutton"> - <xsl:call-template name="inline.gui"/> + + <xsl:template match="lineannotation"> + <xsl:call-template name="inline.span"> + <xsl:with-param name="spanclass" select="'lineannotation'" /> + </xsl:call-template> </xsl:template> - <xsl:template name="inline.comment"> - <xsl:param name="content"> - <xsl:call-template name="anchor"/> - <xsl:call-template name="simple.xlink"> - <xsl:with-param name="content"> - <xsl:apply-templates/> - </xsl:with-param> - </xsl:call-template> - </xsl:param> - <span class="comment"><xsl:copy-of select="$content"/></span> + <xsl:template match="guibutton"> + <xsl:call-template name="inline.span"> + <xsl:with-param name="spanclass" select="'guibutton'" /> + </xsl:call-template> </xsl:template> <xsl:template match="comment"> - <xsl:call-template name="inline.comment"/> + <xsl:call-template name="inline.span"> + <xsl:with-param name="spanclass" select="'comment'" /> + </xsl:call-template> + </xsl:template> + + <xsl:template match="comment|remark"> + <xsl:if test="$show.comments != 0"> + <xsl:call-template name="inline.span"> + <xsl:with-param name="spanclass" select="'remark'" /> + </xsl:call-template> + </xsl:if> </xsl:template> <xsl:param name="generate.toc"> Index: doc-book.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/doc-book.xml,v retrieving revision 1.11 retrieving revision 1.12 diff -u -r1.11 -r1.12 --- doc-book.xml 18 Aug 2002 01:42:12 -0000 1.11 +++ doc-book.xml 21 Aug 2002 19:16:43 -0000 1.12 @@ -11,12 +11,16 @@ <!ENTITY chp_servlets_1 SYSTEM "file://@docpath@/servlets_1.xml"> <!ENTITY chp_servlets_2 SYSTEM "file://@docpath@/servlets_2.xml"> <!ENTITY chp_servlets_3 SYSTEM "file://@docpath@/servlets_3.xml"> + <!ENTITY part_servlets SYSTEM "file://@docpath@/part_servlets.xml"> + <!ENTITY chp_jdbc_testfirst SYSTEM "file://@docpath@/jdbc_testfirst.xml"> <!ENTITY part_testing_guis SYSTEM "file://@docpath@/testing_guis_1.xml"> <!ENTITY chp_random SYSTEM "file://@docpath@/random.xml"> - <!ENTITY notes SYSTEM "file://@docpath@/notes.xml"> + <!ENTITY part_notes SYSTEM "file://@docpath@/notes.xml"> + + <!ENTITY appendix_resources SYSTEM "file://@docpath@/resources.xml"> <!ENTITY patterns SYSTEM "file://@docpath@/patterns.xml"> <!ENTITY glossary SYSTEM "file://@docpath@/glossary.xml"> @@ -46,13 +50,7 @@ &preface; &part_introduction; - - <part status="draft"> - <title>A longer example</title> - &chp_servlets_1; - &chp_servlets_2; - &chp_servlets_3; - </part> + &part_servlets; <part status="draft"> <title>Working with third party libraries</title> @@ -136,8 +134,10 @@ &TODO; </appendix> - ¬es; + &appendix_resources; </part> + + &part_notes; &glossary; </book> Index: testing_guis_1.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/testing_guis_1.xml,v retrieving revision 1.8 retrieving revision 1.9 diff -u -r1.8 -r1.9 --- testing_guis_1.xml 18 Aug 2002 22:44:39 -0000 1.8 +++ testing_guis_1.xml 21 Aug 2002 19:16:43 -0000 1.9 @@ -13,7 +13,13 @@ to a business layer. That's probably why there are no tests for the graphical test runners for <application>JUnit</application>. This is still good advice but it doesn't feel good enough, given how much we say we believe in the benefits of writing the tests first. Now we have a better handle on how to - work with graphical environments (or at least some of them). Let's see something working. + work with graphical environments (or at least some of them). + </para> + <para> + There are now several toolkits available for testing graphical user interfaces, some are listed in + <xref linkend="appendix_resources" />. I won't be using any of them here because I want to get there + from scratch, so you understand how things work. In practice, I'd pick one of the toolkits to get the + job done. Now, let's see something working. </para> </partintro> Index: how_mocks_happened.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/how_mocks_happened.xml,v retrieving revision 1.11 retrieving revision 1.12 diff -u -r1.11 -r1.12 --- how_mocks_happened.xml 20 Aug 2002 16:56:58 -0000 1.11 +++ how_mocks_happened.xml 21 Aug 2002 19:16:43 -0000 1.12 @@ -706,39 +706,44 @@ <para> &TDD; is about design, it's a way of specifying behaviour and the relationships between objects - in a system. What influence does using Mock Objects have on the eventual structure of a system? + in a system. What influence does using Mock Objects have on the eventual structure of the code? Let's start by asking where the significant <quote>design moments</quote> are when developing this way. </para> <para> As we begin a new test, the first question we ask is, <quote>Who would know?</quote> — if we were to implement this functionality, which objects would it affect? The answer - will tell us which objects need to interact with the code under test, and the - the relationships between them. For example, we decided that we needed a <quote>route following - object</quote>, called a <quote>Motor</quote>, so that we could tell whether the route - had been properly constructed; we invented a construct in the software to represent something - in the physical world. This is what Object-Oriented Design is about, and we have a way to - drive that decision from an immediate requirement in a test. A motor is a straightforward - concept, in later examples I'll show how we can be more sophisticated and invent objects to - represent computatational concepts such as callbacks and listeners. + will tell us which objects need to interact with the code under test. For example, we + decided that we needed a <quote>route following object</quote>, a + <classname>Motor</classname>, so that we could tell whether the route + has been properly constructed; we invented a construct in the software to represent something + in the physical world. This sounds to me like Object-Oriented Design (or at least some of it), + and we have found a way to drive that decision from an immediate requirement in a test. + A motor is straightforward, in later examples I'll show how we can be more sophisticated and + invent objects to represent computatational concepts such as callbacks and listeners. </para> <para> - The next decision was how to make the motor object available to the robot object so that it - can move. We could have passed the <classname>Motor</classname> through to the + The next decision was how to connect the motor object to the robot object, so that it + can move. We could have passed the <classname>Motor</classname> through with the <function>goTo()</function> method, but instead we decided to make the <classname>Motor</classname> part of the <classname>Robot</classname> and pass it through in the constructor. That's another design decision and, again, we drove it from the process of writing a test. </para> <para> + We've added a very simple + </para> + + <para> We want to unit test but don't want to expose more of the internals of the object than is necessary, - because that makes the class, and the code that uses it, that little bit harder to change. Across a + because that makes the class, and the code that uses it, that little bit harder to change; across a whole code base, the increased <quote>refactoring drag</quote> can be significant. Our solution - (<quote>Tell, don't ask</quote>) forces us into passing around computation rather than data: we - pass a <quote>route listening object</quote> (a <classname>Motor</classname>) to - the <classname>Robot</classname>, rather than returning a collection of <classname>MoveRequest</classname>. + (<quote>Tell, don't ask</quote>) forces us into passing around computation rather than data, which is + why we used the <quote>route following object</quote>, rather than returning a collection + of <classname>MoveRequest</classname>. </para> <sidebar> + <title>Top-Down Decomposition</title> <para> Some of you may recognise the similarities between &TDD; and the ancient discipline of <glossterm linkend="topdowndecomposition"><quote>Top-Down Decomposition</quote></glossterm>, also known as @@ -751,10 +756,10 @@ <para> Top-Down Decomposition had two powerful insights: to keep each level focussed and consistent, and to drive the design of interfaces from their use rather than their implementation. It also had - a couple of critical failings, because it turned out to be hard to change early design - decisions which, of course, are embedded in the top-level structure, and it's not good at + a couple of critical failings: it turned out to be hard to change early design + decisions because, of course, they're embedded in the top-level structure, and it's not good at encouraging reuse between lower-level components. It looks like &TDD; avoids those failures because - of its emphasis on refactoring and because objects give us better modularity than procedures. + of its emphasis on refactoring. </para> </sidebar> </section> <!-- What about design? --> @@ -825,12 +830,6 @@ out to be the flex points I need as the use of the code evolves. </para> </section> <!-- What does this mean? --> - - <section status="todo"> - <title>Notes</title> - <!-- TODO --> - <para>Top-down decomposition</para> - </section> <section> <title>Conclusions</title> Index: htmlbook.css =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/htmlbook.css,v retrieving revision 1.11 retrieving revision 1.12 diff -u -r1.11 -r1.12 --- htmlbook.css 20 Aug 2002 16:56:58 -0000 1.11 +++ htmlbook.css 21 Aug 2002 19:16:43 -0000 1.12 @@ -1,23 +1,41 @@ /* htmlbook.css, a stylesheet for htmlbook */ -BODY { font-family: Arial, Helvetica, sans-serif} -H1 { margin-top: 5%; } -H2 { margin-top: 3%;} - -DIV.section H2 { font-style: italic; font-weight: normal; } -DIV.section H3 { font-style: normal; font-size: small; } -DIV.tip H3 { font-style: italic; font-weight: normal; font-size: small; } +/* BODY { font-family: Arial, Helvetica, sans-serif} */ +BODY { font-family: "Century Schoolbook", Times, serif; } +H1 { font-family: Arial, Helvetica, sans-serif; margin-top: 5%; } +H2 { margin-top: 3%; font-style: italic;} + +DIV.chapter H2 { font-family: Arial, Helvetica, sans-serif; + font-weight: normal; font-size: 200%; } +DIV.preface H2 { font-family: "Century Schoolbook", Times, serif; + font-weight: normal; font-size: 140%; } +DIV.section H2 { font-family: "Century Schoolbook", Times, serif; + font-weight: normal; font-size: 140%; } +DIV.section H3 { font-style: italic; font-weight: normal; } + +.tip { border: solid black 1px; } +DIV.tip H3 { font-size: 80%; font-style: italic; font-weight: normal; font-size: small; } DIV.tip P { font-size: 80%; } + +.note { border: solid black 1px;} +DIV.note { margin-left: 10%; }; DIV.note TH { font-size: 80%; } DIV.note P { font-size: 80%; } + +.sidebar { border-top: solid black 1px; border-bottom: solid black 1px; + font-family: Arial, Helvetica, sans-serif; font-size: 80%; + padding: 2%; margin-left: 10%; margin-right: 5%; + } +DIV.sidebar P { margin-top: 10px; margin-bottom: 10px; } +DIV.sidebar P.title { margin-top: 0%; font-style: italic; font-size: 130%; } + PRE SPAN.emphasis { font-weight: bold; } -.programlisting { margin-left: 5%; } -.screen { margin-left: 5%; } -.sidebar { border: double grey 5px; font-size: 80%; padding: 2%; margin-left: 10%; margin-right: 10%; } -.tip { border: double black 1px; } -.note { border: double black 1px;} -.screenshot { margin-left: 20%; } -.caption { font-size: 80%; font-style: italic; } -.guibutton { font-weight: bold; } -.comment { font-style: italic; color: red; } \ No newline at end of file +.lineannotation { font-style: italic; font-family: "Century Schoolbook", Times, serif; } +.footnote { margin-left: 5%; text-indent: -3% } +.programlisting { margin-left: 5%; } +.screen { margin-left: 5%; } +.screenshot { margin-left: 20%; } +.caption { font-size: 80%; font-style: italic; } +.guibutton { font-weight: bold; } +.remark { font-style: italic; color: red; } \ No newline at end of file |
From: Steve F. <sm...@us...> - 2002-08-20 16:57:03
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv6760/doc/xdocs Modified Files: glossary.xml introduction.xml how_mocks_happened.xml notes.xml htmlbook.css Log Message: mroe on how_mocks_happened Index: glossary.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/glossary.xml,v retrieving revision 1.3 retrieving revision 1.4 diff -u -r1.3 -r1.4 --- glossary.xml 18 Aug 2002 23:15:29 -0000 1.3 +++ glossary.xml 20 Aug 2002 16:56:57 -0000 1.4 @@ -23,7 +23,25 @@ <glossentry id="refactoring"> <glossterm>Refactoring</glossterm> </glossentry> + + <glossentry id="rup"> + <glossterm>RUP</glossterm> + </glossentry> + </glossdiv> <!-- R --> + + <glossdiv> + <title>S</title> + + <glossentry id="sdd"> + <glossterm>Spaghetti-Driven Development</glossterm> + <glossdef><para> + An Stream Of Consciousness approach to programming influenced by the novels of Woolf, Proust and, + occasionally, Burroughs. The style fell out of favour during the 1980's but has since enjoyed something + of a renaissance in the field of Web Scripting. + </para></glossdef> + </glossentry> + </glossdiv> <!-- S --> <glossdiv> <title>T</title> Index: introduction.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/introduction.xml,v retrieving revision 1.3 retrieving revision 1.4 diff -u -r1.3 -r1.4 --- introduction.xml 12 Aug 2002 23:10:20 -0000 1.3 +++ introduction.xml 20 Aug 2002 16:56:58 -0000 1.4 @@ -68,9 +68,10 @@ </para> <para> - Model-Driven Development is about deferred gratification: I won't write code until I think I have a good - model of the structure of the problem (or the deadline is too close). &TDD; is a different kind of - deferral: I won't spend too much time thinking about the large-scale structure of the problem until I think + Model-Driven Development, such as <glossterm linkend="rup">RUP</glossterm>, is about deferred gratification: + I won't start writing code until I think I have a good enough model of the structure of the problem + (or the deadline is too close). &TDD; is a different kind of deferral: I won't spend too much time + thinking about the large-scale structure of the problem until I think I've learned enough from writing and refactoring the code. When the time comes, the code will tell me how it should be structured and I'll trust myself to see it. Some developers are uncomfortable with this trade-off because it violates everything we've been taught about Good Practice since our first algorithms Index: how_mocks_happened.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/how_mocks_happened.xml,v retrieving revision 1.10 retrieving revision 1.11 diff -u -r1.10 -r1.11 --- how_mocks_happened.xml 18 Aug 2002 23:15:29 -0000 1.10 +++ how_mocks_happened.xml 20 Aug 2002 16:56:58 -0000 1.11 @@ -94,12 +94,12 @@ assertEquals("Should be same move", new MoveRequest(1, MoveRequest.SOUTH), moves.get(1)); }</programlisting> - <sidebar> + <note> <para> You may have noticed that we haven't said anything about the layout of the warehouse. We'll assume for now that the Robot knows about it by some other mechanism. </para> - </sidebar> + </note> <para> This test specifies a simple routing scheme, for moves to adjacent squares, and that we're @@ -247,14 +247,14 @@ assertEquals("Should be same", POSITION, robot.getCurrentPosition()); }</programlisting> - <sidebar> + <note> <para> I'm being inconsistent again by keeping <function>getCurrentPosition()</function>. In my defence, it seems more reasonable to me that a robot should know its current position than that it should keep a list of its recent movements. One day I'll replace this with a <classname>CurrentPositionListener</classname>, or some such technique. Really I will. </para> - </sidebar> + </note> <para> This version of the test still describes the same core specification as before (the @@ -705,14 +705,59 @@ <title>What about design?</title> <para> - &TDD; is about really about design, it's a way of specifying behaviour and the relationships between objects - in a system. What influence does Mock Objects as a technique have on the development of a system? Some of you - may recognise it as a reincarnation of that ancient discipline - <glossterm linkend="topdowndecomposition"><quote>Top-Down Decomposition</quote></glossterm>, also known as - <quote>Structured Programming</quote>. The idea is to start from the top-most level of the program, stub out - the bits that have not yet been implemented, and work your way down. + &TDD; is about design, it's a way of specifying behaviour and the relationships between objects + in a system. What influence does using Mock Objects have on the eventual structure of a system? + Let's start by asking where the significant <quote>design moments</quote> are when + developing this way. + </para> + <para> + As we begin a new test, the first question we ask is, <quote>Who would know?</quote> + — if we were to implement this functionality, which objects would it affect? The answer + will tell us which objects need to interact with the code under test, and the + the relationships between them. For example, we decided that we needed a <quote>route following + object</quote>, called a <quote>Motor</quote>, so that we could tell whether the route + had been properly constructed; we invented a construct in the software to represent something + in the physical world. This is what Object-Oriented Design is about, and we have a way to + drive that decision from an immediate requirement in a test. A motor is a straightforward + concept, in later examples I'll show how we can be more sophisticated and invent objects to + represent computatational concepts such as callbacks and listeners. + </para> + <para> + The next decision was how to make the motor object available to the robot object so that it + can move. We could have passed the <classname>Motor</classname> through to the + <function>goTo()</function> method, but instead we decided to make the <classname>Motor</classname> + part of the <classname>Robot</classname> and pass it through in the constructor. That's another + design decision and, again, we drove it from the process of writing a test. + </para> + <para> + We want to unit test but don't want to expose more of the internals of the object than is necessary, + because that makes the class, and the code that uses it, that little bit harder to change. Across a + whole code base, the increased <quote>refactoring drag</quote> can be significant. Our solution + (<quote>Tell, don't ask</quote>) forces us into passing around computation rather than data: we + pass a <quote>route listening object</quote> (a <classname>Motor</classname>) to + the <classname>Robot</classname>, rather than returning a collection of <classname>MoveRequest</classname>. </para> - </section> + + <sidebar> + <para> + Some of you may recognise the similarities between &TDD; and the ancient discipline of + <glossterm linkend="topdowndecomposition"><quote>Top-Down Decomposition</quote></glossterm>, also known as + <quote>Structured Programming</quote>. The idea is to start from the top-most level of the program, stub out + the bits that have not yet been implemented, and work your way down; repeat until you have a complete + application. In the days when many programmers found procedures and nested scope exotic, + Top-Down Decomposition was a useful technique for avoiding + <glossterm linkend="sdd">Spaghetti-Driven Development</glossterm>. + </para> + <para> + Top-Down Decomposition had two powerful insights: to keep each level focussed and consistent, + and to drive the design of interfaces from their use rather than their implementation. It also had + a couple of critical failings, because it turned out to be hard to change early design + decisions which, of course, are embedded in the top-level structure, and it's not good at + encouraging reuse between lower-level components. It looks like &TDD; avoids those failures because + of its emphasis on refactoring and because objects give us better modularity than procedures. + </para> + </sidebar> + </section> <!-- What about design? --> <!-- TODO --> <comment>Under development</comment> @@ -785,7 +830,6 @@ <title>Notes</title> <!-- TODO --> <para>Top-down decomposition</para> - <para>Design is the choice of object to test</para> </section> <section> Index: notes.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/notes.xml,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- notes.xml 18 Aug 2002 22:44:39 -0000 1.2 +++ notes.xml 20 Aug 2002 16:56:58 -0000 1.3 @@ -4,5 +4,6 @@ <listitem><para>Tests in Ant</para></listitem> <listitem><para><emphasis>DocBook</emphasis>Fix keysym, guibutton, co</para></listitem> <listitem><para>Needs pictures, UML</para></listitem> + <listitem><para><quote>The problem with Oakland, California is that there's no there there</quote> G. Stein</para></listitem> </itemizedlist> </part> Index: htmlbook.css =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/htmlbook.css,v retrieving revision 1.10 retrieving revision 1.11 diff -u -r1.10 -r1.11 --- htmlbook.css 18 Aug 2002 22:44:39 -0000 1.10 +++ htmlbook.css 20 Aug 2002 16:56:58 -0000 1.11 @@ -7,13 +7,16 @@ DIV.section H2 { font-style: italic; font-weight: normal; } DIV.section H3 { font-style: normal; font-size: small; } DIV.tip H3 { font-style: italic; font-weight: normal; font-size: small; } +DIV.tip P { font-size: 80%; } +DIV.note TH { font-size: 80%; } +DIV.note P { font-size: 80%; } PRE SPAN.emphasis { font-weight: bold; } .programlisting { margin-left: 5%; } .screen { margin-left: 5%; } -.sidebar { border: double black 1px; font-size: 80%; padding: 4px; text-align: center; margin-left: 70%; } -.tip { border: double black 1px; font-size: 80%; } -.note { border: double black 1px; font-size: 80%; } +.sidebar { border: double grey 5px; font-size: 80%; padding: 2%; margin-left: 10%; margin-right: 10%; } +.tip { border: double black 1px; } +.note { border: double black 1px;} .screenshot { margin-left: 20%; } .caption { font-size: 80%; font-style: italic; } .guibutton { font-weight: bold; } |
From: Steve F. <sm...@us...> - 2002-08-18 23:15:32
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv5383/doc/xdocs Modified Files: glossary.xml how_mocks_happened.xml Log Message: Started a section on mocks and design Index: glossary.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/glossary.xml,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- glossary.xml 18 Aug 2002 22:44:39 -0000 1.2 +++ glossary.xml 18 Aug 2002 23:15:29 -0000 1.3 @@ -26,6 +26,14 @@ </glossdiv> <!-- R --> <glossdiv> + <title>T</title> + + <glossentry id="topdowndecomposition"> + <glossterm>Top-Down Decomposition</glossterm> + </glossentry> + </glossdiv> <!-- T --> + + <glossdiv> <title>W</title> <glossentry id="wiki"> Index: how_mocks_happened.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/how_mocks_happened.xml,v retrieving revision 1.9 retrieving revision 1.10 diff -u -r1.9 -r1.10 --- how_mocks_happened.xml 18 Aug 2002 22:44:39 -0000 1.9 +++ how_mocks_happened.xml 18 Aug 2002 23:15:29 -0000 1.10 @@ -701,8 +701,20 @@ </section> <!-- Extracting a MockMotor class --> - <!-- TODO --> <comment>Under development</comment> + <section> + <title>What about design?</title> + + <para> + &TDD; is about really about design, it's a way of specifying behaviour and the relationships between objects + in a system. What influence does Mock Objects as a technique have on the development of a system? Some of you + may recognise it as a reincarnation of that ancient discipline + <glossterm linkend="topdowndecomposition"><quote>Top-Down Decomposition</quote></glossterm>, also known as + <quote>Structured Programming</quote>. The idea is to start from the top-most level of the program, stub out + the bits that have not yet been implemented, and work your way down. + </para> + </section> + <!-- TODO --> <comment>Under development</comment> <section> <title>What does this mean?</title> |
From: Steve F. <sm...@us...> - 2002-08-18 22:44:44
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv29932/doc/xdocs Modified Files: glossary.xml testing_guis_1.xml how_mocks_happened.xml patterns.xml notes.xml htmlbook.css Log Message: more work on how mocks happened. Index: glossary.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/glossary.xml,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- glossary.xml 12 Aug 2002 23:07:58 -0000 1.1 +++ glossary.xml 18 Aug 2002 22:44:39 -0000 1.2 @@ -2,6 +2,14 @@ <title>Glossary</title> <glossdiv> + <title>D</title> + + <glossentry id="dtsttcpw"> + <glossterm>Do The Simplest Thing That Could Possibly Work</glossterm> + </glossentry> + </glossdiv> <!-- D --> + + <glossdiv> <title>L</title> <glossentry id="larch"> Index: testing_guis_1.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/testing_guis_1.xml,v retrieving revision 1.7 retrieving revision 1.8 diff -u -r1.7 -r1.8 --- testing_guis_1.xml 12 Aug 2002 23:10:20 -0000 1.7 +++ testing_guis_1.xml 18 Aug 2002 22:44:39 -0000 1.8 @@ -567,6 +567,16 @@ &TODO; </chapter> + <chapter status="todo"> + <title>Notes</title> + <para> + When do you make the flip from testing the GUI from outside to inserting a model that you can mock? + What makes it difficult is that the Swing components are all classes, not interfaces, so it's impossible + to mock up a component and pass it in to the top-level frame. What that drives us to is that the frame + just assembles the pieces and does layout, and the pieces talk to each other via interfaces in a gui model. + What's hard is finding the right time to introduce that gui model. + </para> + </chapter> <appendix id="findNamedComponent" status="todo"> <title>Finding GUI components</title> Index: how_mocks_happened.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/how_mocks_happened.xml,v retrieving revision 1.8 retrieving revision 1.9 diff -u -r1.8 -r1.9 --- how_mocks_happened.xml 18 Aug 2002 01:42:12 -0000 1.8 +++ how_mocks_happened.xml 18 Aug 2002 22:44:39 -0000 1.9 @@ -6,8 +6,8 @@ <para> Mock Objects is a development technique that lets you unit test classes that you didn't think - you could <emphasis>and</emphasis> helps you write better code while doing so. In this chapter - we use a simple example to show a compressed history of how Mock Objects was discovered by + you could <emphasis>and</emphasis> helps you write better code while doing so. This chapter + works through a simple example to show a compressed history of how Mock Objects was discovered by refactoring from conventional unit tests and why they're useful. </para> @@ -17,6 +17,14 @@ <emphasis>inside</emphasis> those mock implementations so that you can validate the interactions between the objects in your test case as they occur. </para> + + <para> + This chapter is unusual in that it's focussed on the tests, it ignores the production code almost completely. + I'll be covering the whole cycle in later chapters but, for now, I want to concentrate + on the thought processes behind a particular approach to test-driven development. This chapter is not about + how to develop routing software for a robot, but how to structure your code so that you can tell if such a + module works. + </para> </section> <!-- Introduction --> <section> @@ -47,11 +55,11 @@ }</programlisting> <para> - This test tells us that the robot is in the same place after the test as it was before the test. - That's an essential requirement, but it's not enough. We can't be sure that the robot hasn't - trundled all the way around the warehouse before returning; we need to know that the robot hasn't - moved. How about if the robot were to store the route it takes each time we call - <methodname>goTo()</methodname>? We could retrieve the route and make sure it's valid. For example: + This test tells us that the robot is in the same place after the run as it was before the run. + That's an essential requirement, but it's not enough. We need to be sure that the robot hasn't + trundled all the way around the warehouse before returning. How about if the robot were to store + the route it takes each time we call <methodname>goTo()</methodname>? Then we could check the number + of steps it took. For example: </para> <programlisting> @@ -67,9 +75,9 @@ }</programlisting> <para> - So far we've specified that the robot will end up in the same place if that's where we ask it to go. We've - also specified that it will store the route from it's most recent move, and something about - the programming interface that it should support. Our next test is to move one point on the grid: + Our first test specifies that the robot will not move if we tell it to go to the same place, that + it will store the route from it's most recent move, and something about the programming interface it + should support. The next meaningful action we can think of is to move just one point on the grid: </para> <programlisting> @@ -88,15 +96,15 @@ <sidebar> <para> - Of course, you've noticed that we haven't said anything about the layout of the warehouse. We'll + You may have noticed that we haven't said anything about the layout of the warehouse. We'll assume for now that the Robot knows about it by some other mechanism. </para> </sidebar> <para> - Now we've also specified the simplest routing scheme, for moves to adjacent squares, and that we're - describing each leg of the route with a <classname>MoveRequest</classname> object. We carry on and - pretty soon we're moving the robot all over the building, for example: + This test specifies a simple routing scheme, for moves to adjacent squares, and that we're + describing each step of the route with a <classname>MoveRequest</classname> object. We carry on and + pretty soon we have tests for moving the robot all over the building, for example: </para> <programlisting> @@ -109,7 +117,7 @@ assertEquals("Should be destination", DESTINATION, robot.getCurrentPosition()); assertEquals("Should be same moves", - makeExpectedLongWayMoves(), robot.getRecentMoves()); + makeExpectedLongWayMoves(), robot.getRecentMoveRequests()); }</programlisting> <para> @@ -124,10 +132,10 @@ <para> Tests like this are effectively small-scale integration tests, they set up pre-conditions and - test post-conditions. There are advantages to this technique because test failures can reveal interesting - dependencies between classes and help to drive refactorings. The disadvantage is that the tests - has no access to the code while it is running. If our last test failed, the error report would - say something like: + test post-conditions but they say nothing about what happens in-between. There are advantages to this technique + because test failures can reveal interesting dependencies between classes, which can suggest useful + refactorings. The disadvantage is that the tests have no access to the code while it is running. If + our last test failed, the failure report would say something like: </para> <screen> @@ -149,16 +157,16 @@ Second, to test this behaviour at all, we had to add some functionality to the real code, to hold all the <classname>MoveRequest</classname>s since the last <methodname>goTo()</methodname>. We don't have any other immediate need for <methodname>getRecentMovesRequests()</methodname> and, - what's worse, we've made an implicit promise to other people who work with the Robot code that we - will store routes, thus shrinking (ever so slightly) our room to manouver. We can mark the method as - test infrastructure, but that sort of notation is clumsy and tends to be ignored in the heat of development. + what's worse, we've made an implicit promise to other people who work with the <classname>Robot</classname> + code that we will store routes, thus shrinking (ever so slightly) our room to manouver. We can mark the method as + test infrastructure, but this sort of notation is clumsy and tends to be ignored in the heat of development. </para> <para> Third, although it's not the case here, test suites based on extracting history from objects tend to need lots of utilities for constructing and comparing collections of values. The need to write external code to manipulate an object is often a warning that its class is getting too busy and - that some behaviour should be moved from the utility to the class. + that some behaviour should be moved from the utility to a real production class. </para> <para> @@ -169,7 +177,7 @@ of avoiding getters? <footnote><para> John Nolan is credited with asking this question at the right time at a software architecture - meeting in London + meeting in London. </para></footnote> </para> </section> <!-- what's wrong with this --> @@ -197,7 +205,7 @@ First we define an interface for <classname>Motor</classname>. We know that there must be some kind of action, which we'll call <methodname>move()</methodname>, that takes some kind of move request as a parameter. We don't yet know what's in that request, so we'll define an empty <classname>MoveRequest</classname> - interface as a placeholder to get us through the compiler. (Of course, in dynamic languages, such as Smalltalk + interface as a placeholder to get us through the compiler. (In dynamic languages, such as Smalltalk and Python, we don't even need to do that.) </para> @@ -216,7 +224,7 @@ </para> <para> - Now we have to decide what the test implementation will do. In this test, we want to be sure that the + We have to decide what the test implementation will do. In this test, we want to be sure that the <classname>Robot</classname> stays in place, so the test <classname>Motor</classname> should simply fail if it receives any requests to move. We can write this test now, before we know anything else about the system. The new version of the test is: @@ -249,10 +257,12 @@ </sidebar> <para> - We have just locked down a little piece of the specification. We can be sure that, however complex our - routing code gets, the <classname>Robot</classname> will not move if asked to go to its current position. - From now on, if there's a bug in the robot routing code that asks the motor to move, the test will fail at the - point that the request is made. The error report might look like: + This version of the test still describes the same core specification as before (the + <classname>Robot</classname> will not move if asked to go to its current position), but it no longer insists + that the <classname>Robot</classname> hold on to its recent movements. The new test is just a little bit more + precise. The other interesting difference is that, if there's a bug in the robot routing code that asks the + motor to move, the test will fail <emphasis>at the point that the request is made</emphasis>. + The error report might look like this: </para> <screen> @@ -269,9 +279,9 @@ <para> which gives a much clearer view of where the error became visible. If finding the problem turns out to be - harder, we can trap the &junit; <classname>AssertionFailedError</classname> in the development + harder, we can trap the &junit; <classname>AssertionFailedError</classname> exception in the development tool to bring up the debugger. Then we can explore the program state at the time of the failure, without - having to step through from the beginning of the test. Of course, this doesn't work in every case but, more + having to step through from the beginning of the test. This technique doesn't work all the time but, more often than not, it takes you to straight to the source of the problem. </para> </section> <!-- Breaking apart the Robot --> @@ -281,14 +291,14 @@ <para> Now let's revisit the second test, moving to an adjacent space. We want to ensure that exactly one move request has been made and that it contains the right values. Testing that the request is made at most - once is straightforward: + once is straightforward. </para> <programlisting> public void testMoveOneStep() { final Position DESTINATION = new Position(1, 0); - Motor mockMotor = new Motor() { + <emphasis>Motor mockMotor = new Motor() { private int moveCount = 0; public void move(MoveRequest request) { @@ -296,7 +306,7 @@ moveCount++; assertEquals("Should be first move", 1, moveCount); } - }; + };</emphasis> Robot robot = new Robot(mockMotor); robot.setCurrentPosition(new Position(0, 0)); @@ -308,10 +318,10 @@ <para> The first assertion will fail if the <classname>Robot</classname> sends the wrong <classname>MoveRequest</classname>. The second assertion will fail if the - <classname>Robot</classname> calls <function>move()</function> more than once. Unfortunately, this test won't + <classname>Robot</classname> calls <function>move()</function> more than once. This test won't fail if the <classname>Robot</classname> doesn't call <function>move()</function> at all, as it won't - try either assertion. We can only check for this kind of failure <emphasis>after</emphasis> - <function>goTo()</function> has finished, so we need to know how many times <function>move()</function> + try either assertion. We can only tell whether this has happened after + <function>goTo()</function> has finished, so we need to record how many times <function>move()</function> was called. Unfortunately, we can't just push <varname>moveCount</varname> up from the anonymous <classname>Motor</classname> class to the test method because a quirk of Java requires such variables to be declared <token>final</token> and we can't change the value of a <type>final int</type>. I can think of @@ -375,12 +385,16 @@ <para> Personally, I prefer the first of these two options because I find it easier to see the boundaries - between the stub and the test classes. As you'll see shortly, I think that makes handling sequences of - events easier. That said, there's not a huge difference, there are plenty of people making effective use - of Self Shunt, and it's more important that your team is consistent about its approach. + between the stub and the test classes. That said, there's not a huge difference, there are plenty of + people making effective use of Self Shunt, and it's more important that your team is consistent + about its approach. <tip> + <title>Be consistent</title> <para> - The most important thing is for the team to have a consistent approach to writing unit tests. + There are many unit testing techniques, and it's important to understand what each one does. + It's more important, however, for the team to have a consistent approach to unit testing than + to pick the perfect strategy each time. This reduces the intellectual overhead when you're + working on an unfamiliar area of the code. </para> </tip> </para> @@ -391,8 +405,8 @@ <title>Testing more than one step</title> <para> - Now we want to test a route that takes two steps which we need to check. Our current test implementation - can only handle one step, so we need to introduce a collection. In this test we need to be sure that the + Now we want to test the contents of a route that takes two steps. Our current test implementation + can only handle one, so we need to introduce a collection. In this test we need to be sure that the steps are taken in the right sequence, so we'll use an <classname>ArrayList</classname> to describe the <classname>MoveRequest</classname>s that we expect the <classname>Robot</classname> to generate. </para> @@ -401,13 +415,14 @@ public void testMoveTwoSteps() { ArrayList expectedMoveRequests = new ArrayList(); - expectedMoveRequests.add(new MoveRequest(1, MoveRequests.SOUTH)); - expectedMoveRequests.add(new MoveRequest(2, MoveRequests.WEST); + expectedMoveRequests.add(new MoveRequest(1, MoveRequest.SOUTH)); + expectedMoveRequests.add(new MoveRequest(2, MoveRequest.WEST); }</programlisting> <para> - We can use to the <varname>expectedMoveRequests</varname> in the mock motor, and cross off the - actual move requests as they arrive. + We can use the <varname>expectedMoveRequests</varname> in the mock motor. Every time an actual + move arrives from the robot, we pull out the next value from the list of expected moves and compare the + two. If there's a mismatch then our routing software has gone wrong and the test fails. </para> <programlisting> @@ -420,8 +435,8 @@ } };</emphasis> - expectedMoveRequests.add(new MoveRequest(1, MoveRequests.SOUTH)); - expectedMoveRequests.add(new MoveRequest(2, MoveRequests.WEST)); + expectedMoveRequests.add(new MoveRequest(1, MoveRequest.SOUTH)); + expectedMoveRequests.add(new MoveRequest(2, MoveRequest.WEST)); Robot robot = new Robot(mockMotor); @@ -431,9 +446,19 @@ assertEquals("Should be destination", new Position(-1, 2), robot.getCurrentPosition()); }</programlisting> + <note> + <para> + In Java <function>remove(0)</function> removes the first element of the list and returns it to the + caller. + </para> + </note> <para> - Which will fail if the robot asks the mock motor to make an incorrect move. We need to add a little more - code to handle the cases where the robot generates too many or too few steps. + We have two more conditions to check. If the robot does not generate enough moves, then not all the + expected moves will be consumed. As before, we need to check at the end that there's nothing left over. + Alternatively, if the robot generates too many moves then calling <function>remove(0)</function> on an + empty list will throw an <classname>IndexOutOfBoundsException</classname> exception. The &junit; framework + will catch that as an error but it's worth translating to a meaningful failure, so we catch the exception and + call <function>fail()</function>. </para> <programlisting> @@ -450,11 +475,10 @@ } }; - expectedMoveRequests.add(new MoveRequest(1, MoveRequests.SOUTH)); - expectedMoveRequests.add(new MoveRequest(2, MoveRequests.WEST)); + expectedMoveRequests.add(new MoveRequest(1, MoveRequest.SOUTH)); + expectedMoveRequests.add(new MoveRequest(2, MoveRequest.WEST)); Robot robot = new Robot(mockMotor); - robot.setCurrentPosition(new Position(0, 0)); robot.goTo(new Position(-1, 2)); @@ -463,124 +487,222 @@ }</programlisting> <para> - Now we have a technique that will test any sequence of steps that we need, including one step or none. We - can rework our original tests for consistency. + Some of you might be asking how introducing a list of expected move requests is better than the + original <quote>playback</quote> version that recorded the most recent movements. The answer is that, + by moving the collection from the production code into the test infrastructure, we've made our design testable + without adding functionality that the system doesn't need. We've kept our options a little bit more open and, + as a side benefit, we have much more useful failure messages. </para> - <programlisting> -public void testMoveOneStep() { - final Position DESTINATION = new Position(1, 0); + </section> <!-- Testing more than one step --> - Motor mockMotor = new Motor() { - public void move(MoveRequest request) { - try { - assertEquals("Should be move", expectedMoveRequests.remove(0), request); - } catch (IndexOutOfBoundsException ex) { - fail("Should be more moves"); - } - } - }; + <section> + <title>Refactoring the tests</title> - expectedMoveRequests.add( new MoveRequest(1, MoveRequest.SOUTH)); + <para> + Now that we have a dynamic list as part of our test infrastructure, we don't really need special test code + to handle the 0-step and 1-step cases any more. I'm going to jump straight to the end result, rather + than take you through the refactorings step by step, to save paper (or pixels, if you're reading + this on-line) but I believe it's straightforward. Here's the entire test class. + </para> + + <programlistingco> + <areaspec> + <area id="fields" coords="3" /> + <area id="robotandmotor" coords="11" /> + <area id="testsameplace" coords="24" /> + <area id="testonestep" coords="29" /> + <area id="testtwosteps" coords="35" /> + <area id="moveandverify" coords="42" /> + </areaspec> + <programlisting> +public class RobotTest extends TestCase { + private Robot robot; + private ArrayList expectedMoveRequests = new ArrayList(); - Robot robot = new Robot(mockMotor); - robot.setCurrentPosition(new Position(0, 0)); - robot.goTo(DESTINATION); + public RobotTest(String name) { + super(name); + } - assertEquals("Should be destination", DESTINATION, robot.getCurrentPosition()); - assertEquals("Should be no more moves", 0, expectedMoveRequests.size()); -} + public void setUp() { + robot = new Robot( + new Motor() { + public void move(MoveRequest request) { + try { + assertEquals("Should be move", expectedMoveRequests.remove(0), request); + } catch (IndexOutOfBoundsException ex) { + fail("Too many moves"); + } + } + }); + } -public void testGotoSamePlace() { - final Position POSITION = new Position(1, 0); + public void testGotoSamePlace() { + final Position POSITION = new Position(1, 1); + moveAndVerifyRobot(POSITION, POSITION); + } - Motor mockMotor = new Motor() { - public void move(MoveRequest request) { - try { - assertEquals("Should be move", expectedMoveRequests.remove(0), request); - } catch (IndexOutOfBoundsException ex) { - fail("Should be more moves"); - } - } - }; + public void testMoveOneStep() { + expectedMoveRequests.add(new MoveRequest(1, MoveRequest.SOUTH)); - Robot robot = new Robot(mockMotor); - robot.setCurrentPosition(POSITION); - robot.goTo(POSITION); + moveAndVerifyRobot(new Position(0, 0), new Position(1, 0)); + } - assertEquals("Should be destination", POSITION, robot.getCurrentPosition()); - assertEquals("Should be no more moves", 0, expectedMoveRequests.size()); -} -</programlisting> + public void testMoveTwoSteps() { + expectedMoveRequests.add(new MoveRequest(1, MoveRequest.SOUTH)); + expectedMoveRequests.add(new MoveRequest(2, MoveRequest.WEST)); + + moveAndVerifyRobot(new Position(0, 0), new Position(-1, 2)); + } + + private void moveAndVerifyRobot(Position start, Position destination) { + robot.setCurrentPosition(start); + robot.goTo(destination); + + assertEquals("Should be destination", destination, robot.getCurrentPosition()); + assertEquals("Should be no more moves", 0, expectedMoveRequests.size()); + } +}</programlisting> - <para> - There's a lot of repetetive code here. I think we should do something about that. + <calloutlist> + <callout arearefs="fields"><para> + The <varname>robot</varname> and <varname>expectedMoveRequests</varname> are used throughout the + class, so we've made them instance fields. + </para></callout> + <callout arearefs="robotandmotor"><para> + There is now just one implementation of the <classname>Motor</classname> for all the tests. We + don't even need to refer to it after we've built it because we're doing all the test configuration + through <varname>expectedMoveRequests</varname>. We set up the <classname>Robot</classname> and + construct its mock <classname>Motor</classname> implementation inline. + </para></callout> + <callout arearefs="testsameplace"><para> + This test says that, if we're going to the same place, then we expect no movement. We've used a + local constant variable <varname>POSITION</varname> to emphasise that the start and destination + are the same. We leave the <varname>expectedMoveRequests</varname> list empty. + </para></callout> + <callout arearefs="testonestep testtwosteps"><para> + These tests make sure that, if the robot moves, it follows the correct route. The only thing a + test needs is start and destination positions and an ordered list of steps. + </para></callout> + <callout arearefs="moveandverify"><para> + All the tests do the same thing: position the robot, ask it to move, and then check the results. + This method implements that sequence. Using <varname>expectedMoveRequests</varname> to describe + the route means that the test for too few moves is always the same. If we'd stuck with the + <quote>playback</quote> version, we would have had to specify the expected route size for each test. + </para></callout> + </calloutlist> + </programlistingco> + + <para> + In practice, I would probably jump straight in using <varname>expectedMoveRequests</varname> for the 0-step + and 1-step cases because I'm used to it and there's not much overhead in the implementation. That's reasonable + provided I don't take too long to write the first test because I'm trying to think too far ahead + and over-generalise. + + <tip> + <title>Go to Lourdes</title> + <para> + Throw away your software crutches occasionally to test your assumptions just one more time; + this is one of the movitivations for the XP rule + <glossterm linkend="dtsttcpw"><quote>Do The Simplest Thing That Could Possibly Work</quote></glossterm>. + I'm always surprised how much better I can do something the next time around after I have more + experience. For example, I've found that writing the examples for this book a real eye-opener + because I've had to work out some of my practices from first principals again. + (Lourdes is a shrine in France famous for healing the lame, the walls are covered in discarded crutches.) + </para> + </tip> </para> - </section> + </section> <!-- Refactoring the tests --> <section> + <title>Extracting a MockMotor class</title> - <!-- TODO --> <comment>Under development</comment> - <title>Under development</title> - <para>As my tests grow, I can refactor the various mock implementations -into a single, more sophisticated MockMotor and use it throughout all the Robot -tests; for example:</para> + <para> + The next thing we might consider doing is to extract the anonymous test implementation of + <classname>Motor</classname> into a <classname>MockMotor</classname> class. This moves the + <classname>expectedMoveRequests</classname> from the tests into the new class. + </para> + + <programlisting> +public class MockMotor implements Motor { + private ArrayList expectedMoveRequests = new ArrayList(); -<programlisting>public void MockMotor implements Motor { - private ArrayList expectedRequests = new ArrayList(); public void move(MoveRequest request) { - assert("Too many requests", this.expectedRequests.size() > 0); - assertEquals("Should be next request", - this.expectedRequests.remove(0), request); + try { + assertEquals("Should be move", expectedMoveRequests.remove(0), request); + } catch (IndexOutOfBoundsException ex) { + fail("Too many moves"); + } } - public void addExpectedRequest(MoveRequest request) { - this.expectedRequests.add(request); + + public void addExpectedMove(MoveRequest request) { + expectedMoveRequests.add(request); } + public void verify() { - assertEquals("Too few requests", 0, this.expectedRequests.size()); + assertEquals("Should be no more moves", 0, expectedMoveRequests.size()); } }</programlisting> -<para>Which makes our tests look like:</para> + <para> + Starting a new class costs a little extra, but it does bring some benefits. + First, it makes the tests a bit more legible because it moves the behaviour + (managing lists of move requests) to the object that should know about it + (the <classname>Motor</classname>). If we rework one of our tests, it comes out as: + </para> -<programlisting>public class TestRobot { - &elipsis; - static final Position ORIGIN = new Position(0, 0); - public void setUp() { - mockMotor = new MockMotor(); - robot = new Robot(mockMotor); - robot.setCurrentPosition(ORIGIN); - } - public void testGotoSamePlace() { - robot.goTo(ORIGIN); + <programlisting> +public class RobotTest extends TestCase { + <emphasis>private MockMotor mockMotor = new MockMotor();</emphasis> + private Robot robot = new Robot(<emphasis>mockMotor</emphasis>); - assertEquals("Should be same", ORIGIN, robot.getCurrentPosition()); - mockMotor.verify(); - } - public void testMoveOnePoint() { - final Position DESTINATION = new Position(1, 0); + public void testMoveTwoSteps() { + <emphasis>mockMotor.addExpectedMove(new MoveRequest(1, MoveRequest.SOUTH)); + mockMotor.addExpectedMove(new MoveRequest(2, MoveRequest.WEST)); </emphasis> - mockRobot.addExpectedRequest(new MoveRequest(1, MoveRequest.SOUTH)); + moveAndVerifyRobot(new Position(0, 0), new Position(-1, 2)); + } - robot.goTo(DESTINATION); + private void moveAndVerifyRobot(Position start, Position destination) { + robot.setCurrentPosition(start); + robot.goTo(destination); - assertEquals("Should be destination", DESTINATION, robot.getCurrentPosition()); - mockMotor.verify(); + assertEquals("Should be destination", destination, robot.getCurrentPosition()); + <emphasis>mockMotor.verify();</emphasis> } - public void testMoveALongWay() { - final Position DESTINATION = new Position(34, 71); + &elipsis; +}</programlisting> - mockMotor.addExpectedRequests(makeExpectedLongWayMoveRequests()); + <para> + I find this version more readable because it expresses clearly that we're setting expectations about + what the robot will do to the motor; it doesn't rely on a side effect, that the + <varname>expectedMoveRequests</varname> is referenced by the fake <classname>Motor</classname> + implementation. The new version also describes those expectations in the language of the motor, + <function>addExpectedMove()</function>, rather than in terms of an object collection. + </para> - robot.goTo(DESTINATION); + <para> + Second, if you have more tests that describe talking to a <classname>Motor</classname>, perhaps + because there are different kinds of robot, then basic code hygiene will force you to pull the + test functionality out into a separate class where it can be reused. One way of looking at the + entire Mock Object approach to testing is that it's simply refactoring assertions out from multiple + test cases. + </para> - assertEquals("Should be destination", DESTINATION, robot.getCurrentPosition()); - mockMotor.verify(); - } -} -</programlisting> + <tip> + <title>Steady promotion</title> + <para> + Often, I evolve a mock class by starting with an anonymous implementation in the test class, + and then extracting a separate mock class if the code becomes anything more than trivial. The mock + class may start as an inner class of the test case (because my development environment makes + that refactoring easy), which I'll promote to package level if I need it elsewhere. + </para> + </tip> + + </section> <!-- Extracting a MockMotor class --> + + <!-- TODO --> <comment>Under development</comment> -</section> <section> <title>What does this mean?</title> @@ -646,6 +768,13 @@ out to be the flex points I need as the use of the code evolves. </para> </section> <!-- What does this mean? --> + + <section status="todo"> + <title>Notes</title> + <!-- TODO --> + <para>Top-down decomposition</para> + <para>Design is the choice of object to test</para> + </section> <section> <title>Conclusions</title> Index: patterns.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/patterns.xml,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- patterns.xml 17 Aug 2002 12:59:10 -0000 1.2 +++ patterns.xml 18 Aug 2002 22:44:39 -0000 1.3 @@ -1,6 +1,28 @@ <glossary status="draft"> <glossdiv> + <title>A</title> + + <glossentry id="arguethetest"> + <glossterm>Argue about the test, not the code</glossterm> + &TODO; + </glossentry> + </glossdiv> <!-- H --> + + <glossdiv> + <title>H</title> + + <glossentry id="howcanwetell"> + <glossterm>How could we tell?</glossterm> + &TODO; + <para> + The first question when adding funtionality is always, "How could we tell if it worked". Often this translates + to "which object would know?", or "What do we verify()?". + </para> + </glossentry> + </glossdiv> <!-- H --> + + <glossdiv> <title>S</title> <glossentry id="selfshunt"> Index: notes.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/notes.xml,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- notes.xml 2 Aug 2002 21:56:03 -0000 1.1 +++ notes.xml 18 Aug 2002 22:44:39 -0000 1.2 @@ -1,8 +1,8 @@ <part status="first cut"> - <title>To Do</title> - <itemizedlist> - <listitem><para>Tests in Ant</para></listitem> - <listitem><para>bash mocks</para></listitem> - <listitem><para><emphasis>DocBook</emphasis>Fix keysym, guibutton, co</para></listitem> - </itemizedlist> + <title>To Do</title> + <itemizedlist> + <listitem><para>Tests in Ant</para></listitem> + <listitem><para><emphasis>DocBook</emphasis>Fix keysym, guibutton, co</para></listitem> + <listitem><para>Needs pictures, UML</para></listitem> + </itemizedlist> </part> Index: htmlbook.css =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/htmlbook.css,v retrieving revision 1.9 retrieving revision 1.10 diff -u -r1.9 -r1.10 --- htmlbook.css 18 Aug 2002 01:42:12 -0000 1.9 +++ htmlbook.css 18 Aug 2002 22:44:39 -0000 1.10 @@ -12,7 +12,8 @@ .programlisting { margin-left: 5%; } .screen { margin-left: 5%; } .sidebar { border: double black 1px; font-size: 80%; padding: 4px; text-align: center; margin-left: 70%; } -.tip { border: double black 1px; font-size: 80%; padding: 2px; } +.tip { border: double black 1px; font-size: 80%; } +.note { border: double black 1px; font-size: 80%; } .screenshot { margin-left: 20%; } .caption { font-size: 80%; font-style: italic; } .guibutton { font-weight: bold; } |
From: Steve F. <sm...@us...> - 2002-08-18 01:42:15
|
Update of /cvsroot/mockobjects/no-stone-unturned/doc/xdocs In directory usw-pr-cvs1:/tmp/cvs-serv2467/doc/xdocs Modified Files: doc-book.xml how_mocks_happened.xml htmlbook.css Log Message: more on how mocks happened Index: doc-book.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/doc-book.xml,v retrieving revision 1.10 retrieving revision 1.11 diff -u -r1.10 -r1.11 --- doc-book.xml 12 Aug 2002 23:10:20 -0000 1.10 +++ doc-book.xml 18 Aug 2002 01:42:12 -0000 1.11 @@ -78,21 +78,37 @@ </chapter> </part> - <part><title>Some other languages</title> + <part status="todo"> + <title>Some other languages</title> + &TODO; <chapter> - <title>C++</title> - &TODO; - <comment><para>with Workshare?</para></comment> + <title>Static languages</title> + <section> + <title>C++</title> + <comment><para>with Workshare?</para></comment> + </section> </chapter> <chapter> - <title>Dynamic and metaprogramming</title> - &TODO; + <title>Languages with reflection</title> + <section> + <title>C#</title> + </section> </chapter> + + <chapter> + <title>Dynamic languages</title> + <section> + <title>Ruby</title> + </section> + </chapter> + <chapter> - <title>bash</title> - &TODO; + <title>Scripts</title> + <section> + <title>bash</title> + </section> </chapter> </part> Index: how_mocks_happened.xml =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/how_mocks_happened.xml,v retrieving revision 1.7 retrieving revision 1.8 diff -u -r1.7 -r1.8 --- how_mocks_happened.xml 17 Aug 2002 12:59:10 -0000 1.7 +++ how_mocks_happened.xml 18 Aug 2002 01:42:12 -0000 1.8 @@ -83,14 +83,13 @@ assertEquals("Should be destination", DESTINATION, robot.getCurrentPosition()); List moves = robot.getRecentMoveRequests(); assertEquals("Should be one move", 1, moves.size()); - assertEquals("Should be same move", - new MoveRequest(1, MoveRequest.SOUTH), moves.get(1)); + assertEquals("Should be same move", new MoveRequest(1, MoveRequest.SOUTH), moves.get(1)); }</programlisting> <sidebar> <para> Of course, you've noticed that we haven't said anything about the layout of the warehouse. We'll - assume for now that it's regular and hard-coded into the Robot. + assume for now that the Robot knows about it by some other mechanism. </para> </sidebar> @@ -240,6 +239,15 @@ assertEquals("Should be same", POSITION, robot.getCurrentPosition()); }</programlisting> + <sidebar> + <para> + I'm being inconsistent again by keeping <function>getCurrentPosition()</function>. In my defence, + it seems more reasonable to me that a robot should know its current position than that it should + keep a list of its recent movements. One day I'll replace this with a + <classname>CurrentPositionListener</classname>, or some such technique. Really I will. + </para> + </sidebar> + <para> We have just locked down a little piece of the specification. We can be sure that, however complex our routing code gets, the <classname>Robot</classname> will not move if asked to go to its current position. @@ -277,7 +285,7 @@ </para> <programlisting> -public void testMoveOnePoint() { +public void testMoveOneStep() { final Position DESTINATION = new Position(1, 0); Motor mockMotor = new Motor() { @@ -314,11 +322,11 @@ the <varname>moveCount</varname> field. </para> <programlisting> -static class AbstractMotorStub implements Motor { +<emphasis>static class AbstractMotorStub implements Motor { int moveCount = 0; -} +}</emphasis> -public void testMoveOnePoint() { +public void testMoveOneStep() { final Position DESTINATION = new Position(1, 0); AbstractMotorStub mockMotor = new AbstractMotorStub() { @@ -334,7 +342,7 @@ robot.goTo(DESTINATION); assertEquals("Should be destination", DESTINATION, robot.getCurrentPosition()); - assertEquals("Should be one move", 1, mockMotor.moveCount); + <emphasis>assertEquals("Should be one move", 1, mockMotor.moveCount);</emphasis> }</programlisting> <para> @@ -343,17 +351,17 @@ </para> <programlisting> -public RobotTest extends TestCase implements Motor { +public RobotTest extends TestCase <emphasis>implements Motor</emphasis> { - // Implementation of Motor + <emphasis>// Implementation of Motor private int moveCount = 0; public void move(MoveRequest request) { assertEquals("Should be move", new MoveRequest(1, MoveRequest.SOUTH), request); moveCount++; assertEquals("Should be first move", 1, moveCount); - } + }</emphasis> - public void testMoveOnePoint() { + public void testMoveOneStep() { final Position DESTINATION = new Position(1, 0); Robot robot = new Robot(this); @@ -361,7 +369,7 @@ robot.goTo(DESTINATION); assertEquals("Should be destination", DESTINATION, robot.getCurrentPosition()); - assertEquals("Should be one move", 1, moveCount); + <emphasis>assertEquals("Should be one move", 1, moveCount);</emphasis> } }</programlisting> @@ -381,40 +389,87 @@ <section> <title>Testing more than one step</title> - <!-- TODO --> <comment>Under development</comment> <para> - Now we want to test a route that requires two steps. + Now we want to test a route that takes two steps which we need to check. Our current test implementation + can only handle one step, so we need to introduce a collection. In this test we need to be sure that the + steps are taken in the right sequence, so we'll use an <classname>ArrayList</classname> to describe the + <classname>MoveRequest</classname>s that we expect the <classname>Robot</classname> to generate. + </para> + + <programlisting> +public void testMoveTwoSteps() { + ArrayList expectedMoveRequests = new ArrayList(); + + expectedMoveRequests.add(new MoveRequest(1, MoveRequests.SOUTH)); + expectedMoveRequests.add(new MoveRequest(2, MoveRequests.WEST); +}</programlisting> + + <para> + We can use to the <varname>expectedMoveRequests</varname> in the mock motor, and cross off the + actual move requests as they arrive. </para> + <programlisting> -public void testMoveOnePoint() { - final Position DESTINATION = new Position(1, 0); +public void testMoveTwoSteps() { + final ArrayList expectedMoveRequests = new ArrayList(); - AbstractMotorStub mockMotor = new AbstractMotorStub() { + <emphasis>Motor mockMotor = new Motor() { public void move(MoveRequest request) { - assertEquals("Should be move", new MoveRequest(1, MoveRequest.SOUTH), request); - moveCount++; - assertEquals("Should be first move", 1, moveCount); + assertEquals("Should be move", expectedMoveRequests.remove(0), request); } - }; + };</emphasis> + + expectedMoveRequests.add(new MoveRequest(1, MoveRequests.SOUTH)); + expectedMoveRequests.add(new MoveRequest(2, MoveRequests.WEST)); Robot robot = new Robot(mockMotor); + robot.setCurrentPosition(new Position(0, 0)); - robot.goTo(DESTINATION); + robot.goTo(new Position(-1, 2)); - assertEquals("Should be destination", DESTINATION, robot.getCurrentPosition()); - assertEquals("Should be one move", 1, mockMotor.moveCount); + assertEquals("Should be destination", new Position(-1, 2), robot.getCurrentPosition()); }</programlisting> + <para> + Which will fail if the robot asks the mock motor to make an incorrect move. We need to add a little more + code to handle the cases where the robot generates too many or too few steps. + </para> + + <programlisting> +public void testMoveTwoSteps() { + final ArrayList expectedMoveRequests = new ArrayList(); + + Motor mockMotor = new Motor() { + public void move(MoveRequest request) { + <emphasis>try {</emphasis> + assertEquals("Should be move", expectedMoveRequests.remove(0), request); + <emphasis>} catch (IndexOutOfBoundsException ex) { + fail("Should be more moves"); + }</emphasis> + } + }; + + expectedMoveRequests.add(new MoveRequest(1, MoveRequests.SOUTH)); + expectedMoveRequests.add(new MoveRequest(2, MoveRequests.WEST)); + + Robot robot = new Robot(mockMotor); + + robot.setCurrentPosition(new Position(0, 0)); + robot.goTo(new Position(-1, 2)); + + assertEquals("Should be destination", new Position(-1, 2), robot.getCurrentPosition()); + <emphasis>assertEquals("Should be no more moves", 0, expectedMoveRequests.size());</emphasis> +}</programlisting> <para> - Third, we could change the way we hold our move requests, we can let the + Now we have a technique that will test any sequence of steps that we need, including one step or none. We + can rework our original tests for consistency. </para> <programlisting> -public void testMoveOnePoint() { +public void testMoveOneStep() { final Position DESTINATION = new Position(1, 0); - final ArrayList expectedMoveRequests = new ArrayList(); Motor mockMotor = new Motor() { public void move(MoveRequest request) { @@ -425,31 +480,47 @@ } } }; - Robot robot = new Robot(mockMotor); - expectedMoveRequests.add(new MoveRequest(1, MoveRequest.SOUTH)); + expectedMoveRequests.add( new MoveRequest(1, MoveRequest.SOUTH)); + + Robot robot = new Robot(mockMotor); robot.setCurrentPosition(new Position(0, 0)); robot.goTo(DESTINATION); assertEquals("Should be destination", DESTINATION, robot.getCurrentPosition()); assertEquals("Should be no more moves", 0, expectedMoveRequests.size()); -}</programlisting> +} - <para> - In this version, I'm holding the details of the expected route in <varname>expectedMoveRequests</varname>. - </para> +public void testGotoSamePlace() { + final Position POSITION = new Position(1, 0); + + Motor mockMotor = new Motor() { + public void move(MoveRequest request) { + try { + assertEquals("Should be move", expectedMoveRequests.remove(0), request); + } catch (IndexOutOfBoundsException ex) { + fail("Should be more moves"); + } + } + }; + + Robot robot = new Robot(mockMotor); + robot.setCurrentPosition(POSITION); + robot.goTo(POSITION); + + assertEquals("Should be destination", POSITION, robot.getCurrentPosition()); + assertEquals("Should be no more moves", 0, expectedMoveRequests.size()); +} +</programlisting> <para> - You can chose which style you prefer. I have to admit that, unreasonably, I'm biased against Self Shunt. - I can never remember which methods belong to the test class and which to the shunt, and it's a little bit - harder to separate out the two aspects later on. That said, the second approach can be a but more complicated - to write. At this level, it's a matter of taste but you should probably use only one style across your team. + There's a lot of repetetive code here. I think we should do something about that. </para> - - <!-- TODO --> <comment>Under development</comment> </section> <section> + + <!-- TODO --> <comment>Under development</comment> <title>Under development</title> <para>As my tests grow, I can refactor the various mock implementations into a single, more sophisticated MockMotor and use it throughout all the Robot @@ -511,7 +582,7 @@ </section> -<section> + <section> <title>What does this mean?</title> <para>My code moved in this direction because I was committed to unit Index: htmlbook.css =================================================================== RCS file: /cvsroot/mockobjects/no-stone-unturned/doc/xdocs/htmlbook.css,v retrieving revision 1.8 retrieving revision 1.9 diff -u -r1.8 -r1.9 --- htmlbook.css 17 Aug 2002 12:59:10 -0000 1.8 +++ htmlbook.css 18 Aug 2002 01:42:12 -0000 1.9 @@ -11,7 +11,7 @@ .programlisting { margin-left: 5%; } .screen { margin-left: 5%; } -.sidebar { border: double black 1px; font-size: 80%; padding: 4px; text-align: center; margin-left: 80%; } +.sidebar { border: double black 1px; font-size: 80%; padding: 4px; text-align: center; margin-left: 70%; } .tip { border: double black 1px; font-size: 80%; padding: 2px; } .screenshot { margin-left: 20%; } .caption { font-size: 80%; font-style: italic; } |