[Httpunit-commit] CVS: httpunit/doc/tutorial build.xml,NONE,1.1 index.html,NONE,1.1 pool_editor_stat
Brought to you by:
russgold
|
From: Russell G. <rus...@us...> - 2001-11-12 20:43:53
|
Update of /cvsroot/httpunit/httpunit/doc/tutorial
In directory usw-pr-cvs1:/tmp/cvs-serv20182/doc/tutorial
Added Files:
build.xml index.html pool_editor_static.html task1.html
task1.zip task1editor-entry.html task1editor-form.html
task1editor-initial.html task1editor-validation.html
tutorial.css web.xml
Log Message:
Added tutorial
--- NEW FILE ---
<?xml version="1.0" ?>
<!-- ======================================================================= -->
<!-- httpunit tutorial build file -->
<!-- ======================================================================= -->
<project name="tutorial" default="test" basedir=".">
<property name="src.dir" value="src" />
<property name="jars.dir" value="jars" />
<property name="classes.dir" value="classes" />
<property name="test.class" value="tutorial.PoolEditorTest" />
<property name="classpath" value="" />
<!-- =================================================================== -->
<!-- Defines the classpath used for compilation and test. -->
<!-- =================================================================== -->
<path id="base.classpath">
<fileset dir="${jars.dir}">
<include name="*.jar"/>
</fileset>
</path>
<!-- =================================================================== -->
<!-- Compiles the source code -->
<!-- =================================================================== -->
<target name="compile">
<mkdir dir="${jars.dir}" />
<mkdir dir="${classes.dir}" />
<javac srcdir="${src.dir}" destdir="${classes.dir}"
debug="on" deprecation="off" optimize="off">
<classpath refid="base.classpath" />
</javac>
</target>
<!-- =================================================================== -->
<!-- Runs the test code -->
<!-- =================================================================== -->
<target name="test" depends="compile">
<java classname="${test.class}" fork="yes" >
<classpath>
<path refid="base.classpath" />
<pathelement location="${classes.dir}" />
<pathelement location="${classpath}" />
</classpath>
</java>
</target>
<!-- =================================================================== -->
<!-- Cleans up generated stuff -->
<!-- =================================================================== -->
<target name="clean">
<delete dir="${classes.dir}" />
</target>
</project>
--- NEW FILE ---
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>HttpUnit Tutorial - intro</title>
<LINK REL="stylesheet" HREF="tutorial.css" TYPE="text/css">
</head>
<body>
<h1>HttpUnit Tutorial</h1>
<p>This tutorial should help you to learn to use HttpUnit to develop and test your web sites. It will take you through the incremental
development of a simple servlet-based web application using a test-first approach. That is, first you are encouraged to
write and execute a test for the next piece of functionality to be added, verify that the test fails (demonstrating that
the functionality is not in place), and then implement the functionality and verify that the test now passes.</p>
<p>The initial tasks will use the <code>ServletRunner</code> class, thus bypassing the need for a web browser or servlet
engine. Later tasks will require a web browser and a servlet engine, and use <code>WebConversation</code>.</p>
<h2>The Application</h2>
<p>Our application is an office sport betting pool. An administrator is designated to manage the pool, which includes
opening and closing the betting. Entrants select the winner in each of the up to 10 games defined by the administrator
and also guess the total score in one of those games, as designated by the administrator. The winner of the pool is the
one who got the most winners correct, with any ties going to the person whose predicted total score in the tie-breaker game
was closest to the actual final score.</p>
<p>We will identify the following uses of the system:</p>
<p class="story-name">1.1 Administrator Opens Pool</p>
<p class="story-contents">The administrator defines up to ten games, specifying the home and away team for each game. He also selects one of
these games as the tie-breaker. Once the data is correct, the administrator opens the pool for betting.</p>
<p class="story-name">1.2 Administrator Closes Pool</p>
<p class="story-contents">After the pool is open, the administrator may at any time close the pool, thus preventing any
further bets or changes to existing bets.</p>
<p class="story-name">1.3 Administrator Posts Results</p>
<p class="story-contents">After the pool is closed, the administrator enters actual scores for each of the games played.
Once all scores have been entered, the administrator posts the results.</p>
<p class="story-name">2.1 Users enters a bet</p>
<p class="story-contents">After the pool is open, a user may sign on and create a bet, predicting the winner of each game
and the total score of the tie-breaker game. The user may change his bet until the pool is closed.</p>
<p class="story-name">2.2 User views results</p>
<p class="story-contents">Once the results have been posted, a user may examine his own bet to see how well he did. The
system will indicate the correct predictions and the user's rank in the pool.</p>
<p class="story-name">2.3 User views standings</p>
<p class="story-contents">Once the results have been posted, a user may view a list of all bidders ranked by number of
correct predictions.</p>
<p>This application is fairly simple, and hardly robust, but contains more than enough interactions to allow us to
explore development and testing with HttpUnit.
<h2>Tutorial Organization</h2>
Each section of the tutorial will address a specific use of the system, and show how HttpUnit and ServletUnit can be used
to write tests which verify that functionality. You should begin each section by copying the initial directory, which includes
an ant build script and some classes that you will need to complete it. Running the ant script will compile the code and
run the tests. After you implement each test, it should fail until you then add the implementation.
</body>
</html>
--- NEW FILE ---
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head><title>UniSports Betting Pool - Definition</title></head>
<body>
<table width='90%' style='background-color=yellow; border-color: black; border-width: 2; border-style: solid'>
<tr><td colspan='2'><b>Cannot open pool for betting:</b></td></tr>
<tr><td width="5"> </td><td>No tiebreaker was specified</td></tr>
</table>
Enter teams for up to 10 games, selecting one as a tie-breaker.
<form method="POST">
<table>
<tr><th>Home Team</th><th>Away Team</th><th>Tiebreaker?</th></tr>
<tr><td><input name="home0"></td><td><input name="away0"></td><td><input type="radio" name="tiebreaker" value="0"/></td></tr>
<tr><td><input name="home1"></td><td><input name="away1"></td><td><input type="radio" name="tiebreaker" value="1"/></td></tr>
<tr><td><input name="home2"></td><td><input name="away2"></td><td><input type="radio" name="tiebreaker" value="2"/></td></tr>
<tr><td bgcolor="red"><input name="home3"></td><td><input name="away3"></td><td><input type="radio" name="tiebreaker" value="3"/></td></tr>
<tr><td><input name="home4"></td><td><input name="away4"></td><td><input type="radio" name="tiebreaker" value="4"/></td></tr>
<tr><td><input name="home5"></td><td><input name="away5"></td><td><input type="radio" name="tiebreaker" value="5"/></td></tr>
<tr><td><input name="home6"></td><td><input name="away6"></td><td><input type="radio" name="tiebreaker" value="6"/></td></tr>
<tr><td><input name="home7"></td><td><input name="away7"></td><td><input type="radio" name="tiebreaker" value="7"/></td></tr>
<tr><td><input name="home8"></td><td><input name="away8"></td><td><input type="radio" name="tiebreaker" value="8"/></td></tr>
<tr><td><input name="home9"></td><td><input name="away9"></td><td><input type="radio" name="tiebreaker" value="9"/></td></tr>
</table>
<input type="submit" name="save" value="Save"/>
<input type="submit" name="save" value="Open Pool"/>
</form>
</body>
</html>
--- NEW FILE ---
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>HttpUnit Tutorial - create pool editor</title>
<LINK REL="stylesheet" HREF="tutorial.css" TYPE="text/css">
</head>
<body>
<h1>Creating the Pool Editor</h1>
<p>Our initial task will be the creation of the simple pool editor, following use case 1.1. We will ignore
security restrictions that permit only the administrator to access this page.
To begin the tutorial, create a working directory and expand <a href="task1.zip">this archive</a> into it.
Either create a jars directory and copy httpunit.jar, Tidy.jar, and xerces.jar into it, or make sure that they are on
your classpath and invoke ant passing the classpath as a property.
You will write your code in the src/tutorial sub-directory.
Once you are ready, proceed to <a href="task1editor-initial.html">step one</a> to begin the tutorial.
--- NEW FILE ---
PK
--- NEW FILE ---
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>HttpUnit Tutorial - create pool editor - step 3</title>
<LINK REL="stylesheet" HREF="tutorial.css" TYPE="text/css">
</head>
<body><h1>Submitting a Form</h1>
<p class="goals">In this step, you will learn how to:
<br />• Check form default parameter values
<br />• Create a request from a form
<br />• Override the default parameter values in a request
<br />• Submit a form and get a response</p>
<p>We now have a form which can generate POST requests to define the betting pool. Our next step will be to build
the code which handles these requests. We will start by simply recording changes, and defer validation until the
administrator attempts to open the pool. For our demonstration application, we will use in-memory persistence only, and
will provide ourselves with a way to clear it so that each <code>JUnit</code> test can run independantly.
<h2>Testing pool display</h2>
<p>The first thing we want to test and implement is the display of the current state of the pool. We will do this by
setting the state directly, using the supplied entity classes and then invoke the form to display the pool. At this time,
we will also add the set up code to clear the pool before each test:</p>
<pre class="test-code">
<b>public void</b> setUp() <b>throws</b> Exception {
BettingPool.reset();
}
<b>public void</b> testPoolDisplay() <b>throws</b> Exception {
BettingPool.getGames()[0].setAwayTeam( "New York Jets" ); // (1) set up data
BettingPool.getGames()[0].setHomeTeam( "Philadelphia Eagles" );
BettingPool.getGames()[2].setAwayTeam( "St. Louis Rams" );
BettingPool.getGames()[2].setHomeTeam( "Chicago Bears" );
BettingPool.setTieBreakerIndex(2);
ServletRunner sr = <b>new</b> ServletRunner( "web.xml" );
ServletUnitClient client = sr.newClient();
WebResponse response = client.getResponse( "http://localhost/PoolEditor" );
WebForm form = response.getFormWithID( "pool" );
assertNotNull( "No form found with ID 'pool'", form );
assertEquals( "Away team 0", "New York Jets", form.getParameterValue( "away0" ) ); // (2) check team names
assertEquals( "Home team 0", "Philadelphia Eagles", form.getParameterValue( "home0" ) );
assertEquals( "Away team 1", "", form.getParameterValue( "away1" ) );
assertEquals( "Home team 1", "", form.getParameterValue( "home1" ) );
assertEquals( "Away team 2", "St. Louis Rams", form.getParameterValue( "away2" ) );
assertEquals( "Home team 2", "Chicago Bears", form.getParameterValue( "home2" ) );
assertEquals( "Tie breaker game", "2", form.getParameterValue( "tiebreaker" ) ); // (3) check radio button
}
</pre>
<p>Here we are:<ol>
<li>Setting up the data that the servlet which read to create the entry form. Here we are using a simple in-memory
persistence strategy. If the servlet accessed the database, we would have to do JDBC calls here.</li>
<li>Reading the names of the teams and comparing them to what we initialized. Note the test for the skipped game to
verify that the defaults come through.</li>
<li>Checking the state of the radio button. Note that we can use the same method <code>WebForm.getParameterValue</code>
for each parameter, no matter its type.</li></ol></p>
<p>As before, the above test should initially fail. We then make it work by changing the loop in <code>printBody</code>
as follows:</p>
<pre class="servlet-code">
BettingPoolGame[] games = BettingPool.getGames();
<b>for</b> (int i = 0; i < games.length; i++) {
pw.println( "<tr><td><input name='home" + i + "' value='" + games[i].getHomeTeam() + "' ></td>" );
pw.println( "<td><input name='away" + i + "' value='" + games[i].getAwayTeam() + "'></td>" );
pw.print( "<td><input type='radio' name='tiebreaker' value='" + i + "'" );
if (i == BettingPool.getTieBreakerIndex()) pw.print( " checked" );
pw.println( " /></td></tr>" );
}
</pre>
<h2>Testing pool entry</h2>
<p>Now that we know we can display the current state of the pool, we will verify that we can use the form to change it
as well:<p />
<pre class="test-code">
<b>public void</b> testPoolEntry() <b>throws</b> Exception {
ServletRunner sr = <b>new</b> ServletRunner( "web.xml" );
ServletUnitClient client = sr.newClient();
WebResponse response = client.getResponse( "http://localhost/PoolEditor" );
WebForm form = response.getFormWithID( "pool" );
assertNotNull( "No form found with ID 'pool'", form );
WebRequest request = form.getRequest( "save", "Save" ); // (1) select a submit button
request.setParameter( "away1", "Detroit Lions" ); // (2) enter values into the form
request.setParameter( "home1", "Denver Broncos" );
request.setParameter( "tiebreaker", "1" );
response = client.getResponse( request ); // (3) submit the form
form = response.getFormWithID( "pool" );
assertEquals( "Away team 0", "", form.getParameterValue( "home0" ) ); // (4) verify the response
assertEquals( "Home team 0", "", form.getParameterValue( "home0" ) );
assertEquals( "Away team 1", "Detroit Lions", form.getParameterValue( "away1" ) );
assertEquals( "Home team 1", "Denver Broncos", form.getParameterValue( "home1" ) );
assertEquals( "Tie breaker game", "1", form.getParameterValue( "tiebreaker" ) );
}
</pre>
<p>This is our first form submission. Take note:<ol>
<li>The first step in submitting a form is obtaining a request from it. Since this form has two named submit buttons,
we must specify which one we want to be included in the request.</li>
<li>We make a call for each parameter that we want to set. Any parameters we do not set will be submitted with their
default values.</li>
<li>We submit the form by asking our client to get a response to the prepared request.</li>
<li>We can use the same methods as above to check the new state of the pool. Alternately, we could have examined the
BettingPool object directly.</li></ol></p>
<p>This test will fail with a 405 response, since we have not defined a handler for the POST message.
We do so now:</p>
<pre class="servlet-code">
<b>protected void</b> doPost( HttpServletRequest request, HttpServletResponse response )
<b>throws</b> ServletException, IOException {
updateBettingPool( request );
response.setContentType( "text/html" );
PrintWriter pw = response.getWriter();
pw.println( "<html><head></head><body>" );
printBody( pw );
pw.println( "</body></html>" );
}
<b>private void</b> updateBettingPool( HttpServletRequest request ) {
BettingPoolGame[] games = BettingPool.getGames();
<b>for</b> (int i = 0; i < games.length; i++) {
games[i].setAwayTeam( request.getParameter( "away" + i ) );
games[i].setHomeTeam( request.getParameter( "home" + i ) );
}
BettingPool.setTieBreakerIndex( getTieBreakerIndex( request ) );
}
<b>private</b> int getTieBreakerIndex( HttpServletRequest request ) {
<b>try</b> {
<b>return</b> Integer.parseInt( request.getParameter( "tiebreaker" ) );
} <b>catch</b> (NumberFormatException e) {
<b>return</b> 0;
}
}
</pre>
<p>Note that we are using the same <code>printBody</code> method as the <code>doGet</code> method does.</p>
<p>We now have the ability to edit the pool entries. Our <a href="task1editor-validation.html">next step</a> is to add
the validation and handle opening the pool to bettors.
</body></html>
--- NEW FILE ---
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>HttpUnit Tutorial - create pool editor - step 2</title>
<LINK REL="stylesheet" HREF="tutorial.css" TYPE="text/css">
</head>
<body>
<h1>Examining a Form</h1>
<p class="goals">In this step, you will learn how to:
<br />• Extract a form from a web page, using its ID
<br />• Examine form attributes
<br />• Check text and radio button input fields
<br />• Check form submit buttons</p>
<p>Now that we can invoke our servlet, the next step will be to produce the <a href="pool_editor_static.html">form</a>
in response to a GET command. This form will POST back to the same URL, allow for entry of 10 pairs of team names,
allow for selection of a tie-breaker game, and allow the user to either save his entries or save them <i>and</i> open
the betting pool. We can write a test for each of these required behaviors.</p>
<h2>Testing the Form action</h2>
<p>Let us add the following test:</p>
<pre class="test-code">
<b>public void</b> testFormAction() <b>throws</b> Exception {
ServletRunner sr = <b>new</b> ServletRunner( "web.xml" );
ServletUnitClient client = sr.newClient();
WebResponse response = client.getResponse( "http://localhost/PoolEditor" );
WebForm form = response.getFormWithID( "pool" ); // (1) obtain the desired form
assertNotNull( "No form found with ID 'pool'", form );
assertEquals( "Form method", "POST", form.getMethod() ); // (2) verify that the form uses POST
assertEquals( "Form action", "", form.getAction() ); // (3) verify that the default action is used
}
</pre>
<p>The significant points in the code are:<ol>
<li>Extracting the form from the page. In this case, we use the ID (which should be unique) attribute to find the form.
We could also just ask for all the forms in the page and select the first one, but this skips the need to count the number
of forms in the page. If the form is not present, we will just get a null value.</li>
<li>Checking that the form uses the POST method.</li>
<li>Checking that the form has no <code>action</code> attribute specified and will therefore default to the URL from
which it was retrieved - in the case, the same servlet that produced it.</li></ol></p>
<p>Again we run the test and fix the reported errors, one by one. The <code>printBody</code> method will now look like this:</p>
<pre class="servlet-code">
<b>private void</b> printBody( PrintWriter pw ) {
pw.println( "<form id='pool' method='POST'>" );
pw.println( "</form>" );
}
</pre>
<p>and we now have two working tests.</p>
<h2>Testing the form contents</h2>
<p>We can now test the input fields in the form:<p />
<pre class="test-code">
<b>public void</b> testFormContents() <b>throws</b> Exception {
ServletRunner sr = <b>new</b> ServletRunner( "web.xml" );
ServletUnitClient client = sr.newClient();
WebResponse response = client.getResponse( "http://localhost/PoolEditor" );
WebForm form = response.getFormWithID( "pool" );
assertNotNull( "No form found with ID 'pool'", form );
<b>for</b> (int i = 0; i < 10; i++) {
assertTrue( "Missing home team " + i, form.isTextParameter( "home" + i ) ); // (1) text parameter
assertTrue( "Missing away team " + i, form.isTextParameter( "away" + i ) ); // (1) text parameter
}
assertEquals( "Tie breaker values",
Arrays.asList( new String[] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" } ),
Arrays.asList( form.getOptionValues( "tiebreaker" ) ) ); // (2) radio button
}
</pre>
<p>The significant points in the code are:<ol>
<li>We verify that a text parameter has been defined with the desired name for each of the 20 possible team values.</li>
<li>Here we verify that we have a set of radio buttons with a specific name and each of the desired values.</li></ol></p>
<p>We satisfy this test by making the <code>printBody</code> method look like this:</p>
<pre class="servlet-code">
<b>private void</b> printBody( PrintWriter pw ) {
pw.println( "<form id='pool' method='POST'>" );
pw.println( "<table>" );
pw.println( "<tr><th>Home Team</th><th>Away Team</th><th>Tiebreaker?</th></tr>" );
<b>for</b> (int i = 0; i < 10; i++) {
pw.println( "<tr><td><input name='home" + i + "'></td>" );
pw.println( "<td><input name='away" + i + "'></td>" );
pw.println( "<td><input type='radio' name='tiebreaker' value='" + i + "'/></td></tr>" );
}
pw.println( "</table>" );
pw.println( "</form>" );
}
</pre>
<h2>Testing the Submit Buttons</h2>
<p>We confirm the presence of the desired submit buttons as follows:<p />
<pre class="test-code">
<b>public void</b> testSubmitButtons() <b>throws</b> Exception {
ServletRunner sr = <b>new</b> ServletRunner( "web.xml" );
ServletUnitClient client = sr.newClient();
WebResponse response = client.getResponse( "http://localhost/PoolEditor" );
WebForm form = response.getFormWithID( "pool" );
assertNotNull( "No form found with ID 'pool'", form );
assertEquals( "Number of submit buttons", 2, form.getSubmitButtons().length ); // (1) count the buttons
assertNotNull( "Save button not found", form.getSubmitButton( "save", "Save" ) ); // (2) look up by name
assertNotNull( "Open Pool button not found", form.getSubmitButton( "save", "Open Pool" ) );
}
</pre>
<p>In this test, please note the following:<ol>
<li>We ask for all the submit buttons associated with the form and just count them. This will ensure that we don't have
more than we want, either.</li>
<li>We ask for each button that we do want by specifying its name and value - which will be used when we submit the form.</li></ol></p>
<p>This test will pass once we modify the end of <code>printBody</code>, inserting lines to generate the submit buttons:</p>
<pre class="servlet-code">
}
pw.println( "</table>" );
pw.println( "<input type='submit' name='save' value='Save' />" );
pw.println( "<input type='submit' name='save' value='Open Pool' />" );
pw.println( "</form>" );
}
</pre>
We now have our form being generated, along with tests to confirm it. In the <a href="task1editor-entry.html">next step</a>,
we will test and code the response to a form submission.
</body></html>
--- NEW FILE ---
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>HttpUnit Tutorial - create pool editor - step 1</title>
<LINK REL="stylesheet" HREF="tutorial.css" TYPE="text/css">
</head>
<body>
<h1>Invoking the Pool Editor</h1>
<p class="goals">In this step, you will learn how to:<br />• Initialize ServletUnit<br />• Invoke a servlet</p>
<p>The first step will simply be to verify that we can register
and access the servlet, which we will name <code>PoolEditorServlet</code>. A GET method to this page should return the editor form
itself, while updates will be handled by a POST method to the same address. Since we are working with servlets, we can
bypass the web server and use the <code>servletunit</code> package to run our tests.</p>
<p>Here is the initial test code:</p>
<pre class="test-code">
<b>package</b> tutorial;
<b>import</b> com.meterware.httpunit.*;
<b>import</b> com.meterware.servletunit.*;
<b>import</b> junit.framework.*;
<b>import</b> tutorial.persistence.*;
<b>public class</b> PoolEditorTest <b>extends</b> TestCase {
<b>public static void</b> main( String args[] ) {
junit.textui.TestRunner.run( suite() );
}
<b>public static</b> TestSuite suite() {
<b>return new</b> TestSuite( PoolEditorTest.<b>class</b> );
}
<b>public</b> PoolEditorTest( String s ) {
<b>super</b>( s );
}
<b>public void</b> testGetForm() <b>throws</b> Exception {
ServletRunner sr = <b>new</b> ServletRunner( "web.xml" ); // (1) use the web.xml file to define mappings
ServletUnitClient client = sr.newClient(); // (2) create a client to invoke the application
client.getResponse( "http://localhost/PoolEditor" ); // (3) invoke the servlet
}
}
</pre>
<p>This code uses <code>JUnit</code> and <code>ServletUnit</code> to verify that a servlet is present at the specified address.
The significant points in the code are:<ol>
<li>Creating the <code>ServletRunner</code> class which represents access to a Servlet application.
The application is defined by an XML file which maps URL information to servlet classes.</li>
<li>Creating a client which can access the application and maintain state across multiple invocations.</li>
<li>Invoking the servlet via its URL. Note that ServletUnit ignores any host and port information. All URL patterns
are treated as being relative to the root ("/").</li></ol></p>
<p>To run this code, you will also need the <a href="web.xml">web.xml</a> file in your current directory. This file
maps the request URL to the Pool Editor servlet.</p>
<p>This code should fail with a <code>HttpNotFoundException</code>, because we have not yet created the servlet class. We
can now proceed to do so. Here is a simple implementation:</p>
<p></p>
<pre class="servlet-code">
<b>package</b> tutorial;
<b>import</b> java.io.*;
<b>import</b> javax.servlet.http.*;
<b>import</b> javax.servlet.ServletException;
<b>import</b> tutorial.persistence.*;
<b>public class</b> PoolEditorServlet <b>extends</b> HttpServlet {
<b>protected void</b> doGet( HttpServletRequest request, HttpServletResponse response )
<b>throws</b> ServletException, IOException {
response.setContentType( "text/html" );
PrintWriter pw = response.getWriter();
pw.println( "<html><head></head><body>" );
printBody( pw );
pw.println( "</body></html>" );
}
<b>private void</b> printBody( PrintWriter pw ) {
pw.println( "A simple page" );
}
}
</pre>
<p>With this code in place, the first test will now pass and we can move to <a href="task1editor-form.html">the next task</a>.</p>
</body>
</html>
--- NEW FILE ---
<html>
<head>
<title>HttpUnit Tutorial - create pool editor - step 4</title>
<LINK REL="stylesheet" HREF="tutorial.css" TYPE="text/css">
</head>
<body>
<h1>Testing servlet internals</h1>
<p class="goals">In this step, you will learn how to:
<br />• Access a servlet directly during an invocation
<br />• Extract a table from a web response, using its contents
<br />• Examine the contents of an HTML table
<br />• Verify that fields are marked read-only</p>
<p>We are nearly done with the pool editor. We can now use it to define the contents of the pool; however, we can
only permit the administrator to open the pool for betting if it is valid. We therefore have to define the validity rules.
For this tutorial, the only rules that we will insist on is that all teams must have opponents, and that only a game with
a pair of teams may be selected as the tie-breaker.</p>
<p>We must also prevent edits to the pool once it has been opened.</p>
<h2>Testing bad inputs</h2>
<p>Validation is often complicated, since we have to not only check the data against our validation rules, we also have
to recognize the need to validate, and modify the output to show any errors. It would be nice if we could break this into
pieces and build one at a time. With <code>ServletUnit</code>, we can:<p />
<pre class="test-code">
<b>public void</b> testPoolValidation() <b>throws</b> Exception {
ServletRunner sr = new ServletRunner( "web.xml" );
ServletUnitClient client = sr.newClient();
WebResponse response = client.getResponse( "http://localhost/PoolEditor" );
WebForm form = response.getFormWithID( "pool" );
WebRequest request = form.getRequest( "save", "Open Pool" );
request.setParameter( "away1", "Detroit Lions" );
request.setParameter( "home1", "Denver Broncos" );
request.setParameter( "home2", "Baltimore Ravens" );
request.setParameter( "tiebreaker", "3" );
InvocationContext context = client.newInvocation( request ); // (1) create an invocation context
PoolEditorServlet servlet = (PoolEditorServlet) context.getServlet(); // (2) locate the invoked servlet
servlet.updateBettingPool( context.getRequest() ); // (3) ask servlet to update the data
String[] errors = servlet.getValidationErrors(); // (4) ask servlet to check the data
assertEquals( "Number of errors reported", 2, errors.length );
assertEquals( "First error", "Tiebreaker is not a valid game", errors[0] );
assertEquals( "Second error", "Game 2 has no away team", errors[1] );
}
</pre>
<p>This test starts out like all the others, but once we have created the request, things venture into new territory:<ol>
<li>Rather than simply asking for the response, we create a context in which we can step through the invocation of the servlet.
This uses the same mechanisms to locate and initialize the servlet, request, and response objects, but does not actually
invoke the servlet to process the request.</li>
<li>We retrieve the initialized servlet and cast it in order to get at its intermediate methods.</li>
<li>We call the <code>updateBettingPool</code> method (which we need to make package-accessible), passing the request
object found in the context.</li>
<li>We can now call a new method in the servlet which will return an array of error messages, which we can compare against
our expected values for them.</li></ol></p>
<p>To make this test pass, we just have to create a single new method in the servlet:</p>
<pre class="servlet-code">
String[] getValidationErrors() {
ArrayList errorList = <b>new</b> ArrayList();
BettingPoolGame game = BettingPool.getGames()[ BettingPool.getTieBreakerIndex() ];
<b>if</b> (game.getAwayTeam().length() == 0 || game.getHomeTeam().length() == 0) {
errorList.add( "Tiebreaker is not a valid game" );
}
BettingPoolGame[] games = BettingPool.getGames();
<b>for</b> (int i = 0; i < games.length; i++) {
<b>if</b> (games[i].getAwayTeam().length() == 0 && games[i].getHomeTeam().length() != 0) {
errorList.add( "Game " + i + " has no away team" );
} else <b>if</b> (games[i].getAwayTeam().length() != 0 && games[i].getHomeTeam().length() == 0) {
errorList.add( "Game " + i + " has no home team" );
}
}
String[] errors = (String[]) errorList.toArray( <b>new</b> String[ errorList.size() ] );
<b>return</b> errors;
}
</pre>
<h2>Displaying the error messages</h2>
<p>Once we are sure of our validation logic, we need to have the error messages displayed. We will arrange to have any error message displayed in the top of row of the table, and we will highlight any cells
containing bad inputs. We therefore ask for the response from the bad open pool request:<p />
<pre class="test-code">
<b>public void</b> testBadPoolOpen() <b>throws</b> Exception {
ServletRunner sr = <b>new</b> ServletRunner( "web.xml" );
ServletUnitClient client = sr.newClient();
WebResponse response = client.getResponse( "http://localhost/PoolEditor" );
WebForm form = response.getFormWithID( "pool" );
WebRequest request = form.getRequest( "save", "Open Pool" ); // (1) select a submit button
request.setParameter( "away1", "Detroit Lions" ); // (2) enter bad values into the form
request.setParameter( "home1", "Denver Broncos" );
request.setParameter( "home2", "Baltimore Ravens" );
request.setParameter( "tiebreaker", "3" );
response = client.getResponse( request ); // (3) submit the form
WebTable errorTable = response.getTableStartingWith( "Errors Detected:" ); // (4) Look for error table
assertNotNull( "No errors reported", errorTable );
String[][] cells = errorTable.asText(); // (5) Convert non-empty cells to text
assertEquals( "Number of error messages provided", 2, cells.length - 1 );
assertEquals( "Error message", "Tiebreaker is not a valid game", cells[1][0] );
assertEquals( "Error message", "Game 2 has no away team", cells[2][0] );
}
</pre>
<p>Note:<ol>
<li>We select the "Open Pool" button to be included with the form submission.</li>
<li>We then enter known bad values.</li>
<li>We want the response when we submit the form changes.</li>
<li>We expect to find them in a table which we can recognize because its upper-leftmost non-empty cell which contain
a known string.</li>
<li>Since we want to examine the textual content of any non-empty cells in the table, we ask that the table be converted
to a two-dimensional string array. In this case, there should only be one non-blank cell in each row.</li></ol></p>
<p>This test passes once we modify the end of the <code>doPost</code> method :</p>
<pre class="servlet-code">
pw.println( "<html><head></head><body>" );
<b>if</b> (request.getParameter( "save" ).equals( "Open Pool" )) {
String[] errors = getValidationErrors();
<b>if</b> (errors.length != 0) reportErrors( pw, errors );
}
printBody( pw );
pw.println( "</body></html>" );
}
<b>private void</b> reportErrors( PrintWriter pw, String[] errors ) {
pw.println( "<table width='90%' style='background-color=yellow; " );
pw.println( " border-color: black; border-width: 2; border-style: solid'>" );
pw.println( "<tr><td colspan='2'><b>Cannot open pool for betting:</b></td></tr>" );
<b>for</b> (int i=0; i < errors.length; i++) {
pw.println( "<tr><td width='5'>&nbsp;</td><td>" + errors[i] + "</td></tr>" );
}
pw.println( "</table>" );
}
</pre>
<p>Note that we are actually displaying <i>two</i> cells for each error. The first is blank, and is simply used for formatting,
as many web designers tend to do. The test code will ignore this, so that if the page is later modified to use stylesheets
to control its formatting, the test will be unaffected. For this same reason, the tests in this tutorial tend to ignore
formatting issues in general, and only look at structural elements.
<h2>Closing the pool</h2>
<p>If everything is valid, we should be able close the pool. This will be reflected by a change in state of the BettingPool
object - which will later be used to change the options available to the users - and should forbid future changes to
the pool itself. We will test this by verifying that the "save" submit buttons are no longer enabled:<p />
<pre class="test-code">
<b>public void</b> testGoodPoolOpen() <b>throws</b> Exception {
ServletRunner sr = <b>new</b> ServletRunner( "web.xml" );
ServletUnitClient client = sr.newClient();
WebResponse response = client.getResponse( "http://localhost/PoolEditor" );
WebForm form = response.getFormWithID( "pool" );
WebRequest request = form.getRequest( "save", "Open Pool" );
request.setParameter( "away1", "Detroit Lions" );
request.setParameter( "home1", "Denver Broncos" );
request.setParameter( "away3", "Indianapolis Colts" );
request.setParameter( "home3", "Baltimore Ravens" );
request.setParameter( "tiebreaker", "3" );
client.getResponse( request ); // (1) ignore the response
response = client.getResponse( "http://localhost/PoolEditor" ); // (2) retrieve the page separately
form = response.getFormWithID( "pool" );
assertNull( "Could still update the pool", form.getSubmitButton( "save" ) ); // (3) look for the buttons
try {
request = form.getRequest();
request.setParameter( "home3", "Philadelphia Eagles" ); // (4) try to change an entry
fail( "Could still edit the pool" );
} catch (IllegalRequestParameterException e) {}
}
</pre>
<p>Note:<ol>
<li>We are not interested in the response this time because we may ultimately have the browser forwarded to the main page.</li>
<li>We come back to the form as though we were planning on editing it anew.</li>
<li>We want to ensure that the submit buttons are disabled so the user cannot submit the form.</li>
<li>We also verify that the fields are now marked readonly, which would prevent us from changing them. If the
exception is not thrown, the test will be marked as failing.</li></ol></p>
<p>We have to make changes in two places to make this behavior work. The following code change to <code>printBody</code>
makes the form display read-only once the pool is open:</p>
<pre class="servlet-code">
for (int i = 0; i < games.length; i++) {
pw.println( "<tr><td><input name='home" + i + "' value='" + games[i].getHomeTeam() + "'" + getReadOnlyFlag() + "></td>" );
pw.println( "<td><input name='away" + i + "' value='" + games[i].getAwayTeam() + "'" + getReadOnlyFlag() + "></td>" );
pw.print( "<td><input type='radio' name='tiebreaker' value='" + i + "'" + getReadOnlyFlag() );
if (i == BettingPool.getTieBreakerIndex()) pw.print( " checked" );
pw.println( " /></td></tr>" );
}
pw.println( "</table>" );
if (BettingPool.isEditable()) {
pw.println( "<input type='submit' name='save' value='Save' />" );
pw.println( "<input type='submit' name='save' value='Open Pool' />" );
}
pw.println( "</form>" );
}
private String getReadOnlyFlag() {
return BettingPool.isEditable() ? "" : " readonly";
}
</pre>
<p>and we have to make a small change to <code>doPost</code> in order to mark the pool open:</p>
<pre class="servlet-code">
String[] errors = getValidationErrors();
if (errors.length != 0) reportErrors( pw, errors );
else {
BettingPool.openPool();
}
}
</pre>
<p>The pool editor is now complete.</p>
</body></html>
--- NEW FILE ---
PRE { font-family: "courier new", courier; font-size: .75em;
margin-left: 2%; margin-right: 2%;
padding-top: .5em; padding-bottom: .5em; padding-right: .5em; padding-left: .5em;
border-color: black; border-width: 2; border-style: solid }
PRE.test-code { background-color: #cccccc }
PRE.servlet-code { background-color: #00eeee }
OL { margin-top: 0 }
P.story-name { margin-left: 3em; margin-bottom: 0; font-weight: bold }
P.story-contents { margin-left: 3em; margin-top: 0 }
P.goals { font-family: helvetica, ariel, sans-serif; font-size: x-small;
background-color: yellow; border-color: black; border-style: solid;
border-top-width: 2px; border-bottom-width: 2px; border-left-width: 0px; border-right-width: 0px }
UL { margin-top: 0 }
--- NEW FILE ---
<?xml version='1.0' encoding='ISO-8859-1'?>
<web-app>
<servlet>
<servlet-name>Editor</servlet-name>
<servlet-class>tutorial.PoolEditorServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Editor</servlet-name>
<url-pattern>/PoolEditor</url-pattern>
</servlet-mapping>
</web-app>
|