|Industrial Logic Papers A Test-First Design eXPerience|
A Test-First Design eXPerience
Version: 1.0 Author: Joshua Kerievsky Created: March 12, 2000
Testing Web Pages
Step 1. Write my first test. I must admit, this is wierd. It is natural to start writing the actual implmentation code, but to start by writing a test is odd. What do I call the test code class name? Well, since I have some idea that what I'm going to create is going to be called a Page, I'm going to call this test code PageUnitTest.java. And here's what I wrote:
I go to compile this code and it doesn't even compile. Says that Page doesn't exist. Well, I knew that. I just had to let the compiler tell me. So I create the Page class as follows:
I compile and then go back to PageUnitTest to compile it. The compile now succeeds. So I execute it as a test. Since I use JUnit, the way I do this is I execute the following:
java junit.textui.TestRunner PageUnitTestWhen this runs, it outputs the following:
. Time: 0.50 OK (1 tests)
Great. Now I feel good. I'm doing test-first design and I've passed a test! Now it is time to do some real work.
I think of a Page as a representation of a web page, which is returned from a Web server via a Servlet, or JSP (Java Server Pages) or CGI program. I have to specify a URL for this page, and for more complex pages, I'll need to specify arguments that get passed to the server, like when you enter your username and password. In the back of my mind, I want to keep this all very simple, since the code is ultimately going to be driven by non-technical Customers.
Ok, so I will name Pages. I pass a name into the Page constructor. I compile, and it fails, so I hop over to the Page code, and add a Name to the constructor.
I write this:
Then I modify the test to simply pass in a name: Page page = new Page("HelloServlet");
I compile the Page and test code and run it and all is good. But then I have this moment of guilt. Since I type pretty fast, I threw in the toString() method on Page, when I didn't really need it. XP has this expression which says You Aren't Gonna Need It (YAGNI). Sure, a toString() method can be useful, but frankly, I do not need it now so why have I written it? I go back and delete it toString() from Page.java.
Now, I decide to make some bigger steps. I want to get the contents of a Page and then get a reference to that content to see that it is not null. So I write more test code. I add this method to PageUnitTest:
Now, this doesn't compile, so I have to go add methods retrieve() and contents() to Page.java. I do that, writing almost no code accept for the declarations, and I have contents() return null:
Ok, now I compile and run PageUnitTest and it fails, telling me:
..F Time: 0.0 FAILURES!!! Test Results: Run: 2 Failures: 1 Errors: 0 There was 1 failure: 1) PageUnitTest$2.testPageRetrieval junit.framework.AssertionFailedError at junit.framework.TestCase.fail(TestCase.java:233) at junit.framework.TestCase.assert(TestCase.java:95) at junit.framework.TestCase.assertNotNull(TestCase.java:174) at junit.framework.TestCase.assertNotNull(TestCase.java:168) at PageUnitTest.testPageRetrieval(PageUnitTest.java:41) at PageUnitTest$2.runTest(PageUnitTest.java:24) at junit.framework.TestCase.runBare(TestCase.java:299) at junit.framework.TestResult.run(TestResult.java:66) at junit.framework.TestCase.run(TestCase.java:289) at junit.framework.TestSuite.run(TestSuite.java:120) at junit.textui.TestRunner.doRun(TestRunner.java:34) at junit.textui.TestRunner.start(TestRunner.java:131) at junit.textui.TestRunner.main(TestRunner.java:57)
Ok, what now? Well, I haven't done anything real yet. Time to do something real. For starters, I'll need a URL to use so that I can go get some content. I need to pass this to Page, and the first thought is to do it in the constructor, but passing a Name and a URL bugs me - seems redundant, since the name could be used to look up the URL. So I decide to stop thinking so much and just pass it to Page in a set method.
After updating this test code, I go back to Page.java to update it with the set method. Nothing complicated at this point so I'll keep going.
Now I want to really make Page.java do something, so I can see if my test works. I want a Page instance to be able to go get some content from a real web page. Here's what I add:
There is no need to change the test code at this point. So I run it against this new version of Page. But before I can run the test, I have to kick-off JRun, which is responsible for serving up my Servlet, HelloServlet. I kick off JRun, and I'm ready. I'm now expecting the test to work, but I'm surprised to find that it does not!
On further inspection, I see that a File Not Found exception is being thrown because the URL I passed to Page.java was wrong -- I wrote "servlets" instead of "servlet". Ok, no big deal, I fix the URL and pass both of the tests in PageUnitTest. But this error gets me thinking. Perhaps I need a test to confirm when a URL is bad? I set out to write that test. When I'm done I run the tests again - and now all three pass. Here's the latest test code:
Ok, now I'm starting to feel a rhythm to this development style. I think about what must come next. What am I currently testing? I'm testing that I can
testPageContents makes sure that the page that is returned contains the contents I expect. I run the tests, and all of them pass.
Ok, I now feel that I have a pretty good Page class. It is simple and direct. I know that I want to implement the more complicated PageGet and PagePost classes next. And I know that I'll want to add more functionality to Page at some point. But I am keeping my goals in mind and my goals are to be able to test simple Web pages, pages submitted with an HTTP Get, and pages submitted with an HTTP Put.
This will conclude this journey, but check soon for the continuation - we'll build PageGet and PagePost together, and see what becomes of Page.java.