Title: Testing Webbased applications
1Testing Web-based applications
- Typically, a multi-layered architecture is used.
Three primary layers, typically on different
hosts - Client
- often uses a web browser, but could also be an
client application - sends requests for web pages
- receives response
- Database
- stores persistent data such as item catalogues,
transaction records, etc. - Application server
- receives requests and constructs a web page
response - handles access control, session management, etc.
Client
Application server
Database
2The Client layer
- Web pages are represented by HTML, the HyperText
Markup Language. - The protocol to send and receive web pages from a
web server is HTTP, the HyperText Transfer
Protocol. - Whats useful to know about HTTP in this context?
- It was intended as a request response protocol
without any state. - It is entirely text-based and (somewhat)
human-readable.
3Application Servers
- Typical functions
- Management of concurrent sessions and
transactions - Construction of dynamic and static web pages
- Arrange for storage and retrieval of persistent
data - Handling of business logic
- Security
4For this presentation
- Some assumptions on the environment for the
purposes of this presentation - Code is based on the Java Enterprise Edition (EE)
software development kit - Use of a web application server that conforms to
the Java EE specification
5Java Application Servers
- Java EE servers use the concept of a container
in which processing of a session is handled. - Java classes are deployed into a container, and
then interact with the container to provide
functionality for the server. - The container manages the creation and life cycle
of the objects deployed within, and access to
these objects.
6Java Application Servers
Container
HTTP request
Servlet
Filter
Enterprise JavaBean (EJB)
Client / Browser
Java Server Page (JSP)
Tag Library
HTTP response, containing HTML
Application server
DB
7Server options
- Production-capable server
- Apache Tomcat, Sun JAS, Glassfish (for EE 5),
JBoss, BEA, IBM WebSphere, Oracle Application
Server, JOnAS, ... - Simplified server
- Jetty
- No server
- Mock object generator that provides mocks for the
HTTP interface or the DB interface.
8Part 1 Testing the client
- If the purpose of testing is to test behaviour at
the client, then various approaches are possible. - Use an actual production-capable application
server (including possibly even the database
layer). - Use an application server (perhaps a with stubs
for web pages - Web pages are empty or nearly so, with no dynamic
content. - Replace the application server with a stub or
mock object that works with the HTTP protocol.
The mock connection is the interface to the
client. - Test client objects directly with JUnit.
9Testing the client
HTTP request
Embedded Application Server
Client / Browser
JUnit Test Case
HTTP response, containing HTML
10Testing the client
HTTP request
Mock Connection
Client / Browser
JUnit Test Case
HTTP response, containing HTML
11HTTP primary commands
- GET request a resource
- GET /awilliam/csi5118 HTTP/1.1Host
www.site.uottawa.ca - POST submit data for processing
- POST /index.html HTTP/1.1Host
www.example.com...Content-type
application/x-www-form-urlencoded...searchwordf
indmeoptionsearchsubmitgo
File to retrieve
File from which data was posted
Form data
12HTTP responses
- HTTP returns a numeric 3-digit code, header
information, plus content. - Frequent codes
- 200 OK request was satisfied, and message
contains content. - HTTP/1.1 200 OK ... Content-length
43794 Content-type text/html ... lthtmlgt ...
lt/htmlgt - 404 Not found the resource requested could not
be located.
Success
Content information
Web page in HTML
13Potential test purposesat the Client Interface
- Check that when a user action occurs, the correct
data is put into an HTTP request - Link has correct uniform resource locator (URL)
address. - Data in POST command is as expected, and
formatted correctly. - Check that when an HTTP request is sent to the
server, the response is as expected. - HTTP response is correct
- HTML response is correct
- Search for particular items within a web page to
see if they are included (especially for
dynamically generated pages) - Correct page
- Correct page elements (buttons, etc.)
- Data is as expected.
14Sample Web client that returnsa string read
from a web server
- public class WebClient
-
- public String getContent( URL url )
-
- StringBuffer content new StringBuffer()
- try
-
- HttpURLConnection connection
- (HttpURLConnection)
url.openConnection( ) - connection.setDoInput( true )
- InputStream is connection.getInputStrea
m( ) - byte buffer new byte2048
- int count
- while ( -1 ! ( count is.read( buffer
) ) ) -
- content.append( new String( buffer,
0, count ) ) -
-
- catch ( IOException e ) return null
15Client test strategies
- To test this client, we have to arrange for some
known data to appear from the URL, and be sure
that the client reads it correctly. - Two approaches
- Use an embedded server that we can control.
- Use a stub or mock object for the connection.
16Test strategy 1 Embedded server
HTTP request
Embedded Application Server
Client / Browser
JUnit Test Case
HTTP response, containing HTML
17Jetty
- Jetty is a small application server that can be
embedded within a Java application. - http//www.mortbay.org/
- For running JUnit, Jetty can be started within
the same virtual machine. - Jetty can be provided with various contexts and
handlers - Essentially, a context defines a relative URL for
which the Jetty server will accept requests. - Handlers tell Jetty how to construct responses.
18Setup for Embedded Jetty Server
- public class WebClientTest
-
- private static HttpServer server
- _at_BeforeClass
- public static void setUpBeforeClass( ) throws
Exception -
- server new HttpServer()
- SocketListener listener new
SocketListener() - listener.setPort( 8080 )
- server.addListener( listener )
- HttpContext context1 new HttpContext()
- context1.setContextPath( "/testGetContentOK"
) - context1.addHandler( new TestGetContentOKHan
dler() ) - server.addContext( context1 )
- HttpContext context2 new HttpContext()
- context2.setContextPath( "/testGetContentNot
Found" ) - context2.addHandler( new NotFoundHandler()
) - server.addContext( context2 )
-
Handler for requests
URL for the context will be httplocalhost8080/te
stGetContentOK
19Jetty Handlers
- public class TestGetContentOKHandler extends
AbstractHttpHandler -
- public void handle( String pathInContext,
String pathParams, - HttpRequest theRequest,
HttpResponse theResponse ) - throws HttpException, IOException
-
- OutputStream out theResponse.getOutputStre
am( ) - ByteArrayISO8859Writer writer new
ByteArrayISO8859Writer( ) - writer.write( "It works" )
- writer.flush( )
- theResponse.setIntField( HttpFields.__Conten
tLength, - writer.size( ) )
- writer.writeTo( out )
- out.flush( )
- theRequest.setHandled( true )
-
20Sample test Teardown
- _at_Test
- public void testGetContentOK( ) throws
MalformedURLException -
- WebClient client new WebClient( )
- URL url new
- URL("http//localhost8080/testGetConten
tOK") - String expected "It works"
- String actual client.getContent( url )
- assertEquals( expected, actual )
-
- _at_AfterClass
- public static void tearDownAfterClass( ) throws
Exception -
- server.stop()
21Strategy 2 No server
HTTP request
Stub Connection
Client / Browser
JUnit Test Case
HTTP response, containing HTML
22Strategy 2 Implementation
HTTP request
Client / Browser
Stub URL Stream Handler
JUnit Test Case
URL
Stub URL Connection
HTTP response, containing HTML
Stub Stream Handler Factory
23Stub for the URL connection
- public class StubHttpURLConnection extends
HttpURLConnection -
- private boolean isInput true
- protected StubHttpURLConnection( URL url )
-
- super( url )
-
- public InputStream getInputStream( ) throws
IOException -
- ByteArrayInputStream bais
- new ByteArrayInputStream( new String(
"It works" ) - .getBytes( ) )
- return bais
-
24Sample test Setup
- _at_BeforeClass
- public static void setUpBeforeClass( ) throws
Exception -
- URL.setURLStreamHandlerFactory(
- new StubStreamHandlerFactory() )
-
- _at_Test
- public void testGetContentOK( ) throws
MalformedURLException -
- WebClient client new WebClient( )
- URL url new
- URL("http//localhost8080/testGetConten
tOK") - String expected "It works"
- String actual client.getContent( url )
- assertEquals( expected, actual )
-
25Part 2 Testing Application Server Classes
- In-container approach
- This is the actual environment in which the class
would be run. - Requires deploying classes to an application
server (complex, time-consuming setup) - Access to classes for test purposes is restricted
by the container. - Out-of-container approach
- Requires stubs or mock objects for interactions
with the container. - Not the actual running environment.
- Once the container environment is simulated,
tests can be run quickly.
26Java Application Servers (reprise)
Container
HTTP request
Servlet
Filter
Enterprise JavaBean (EJB)
Client / Browser
Java Server Page (JSP)
Tag Library
HTTP response, containing HTML
Application server
DB
27Servlets
- Servlets are a mechanism for an application
server to construct dynamic web page content. - Example
- User enters a value into a text field on a web
page and clicks a submit button. - An HTTP command is constructed by the browser and
sent to the server. - The application server will parse the HTTP
command, determine which session the command
belongs to, and construct an HttpServletRequest
object containing the request information. - The servlets function is to create an
HttpServletResponse object that contains
information needed to create the HTTP reply that
contains the HTML to be displayed in the users
browser.
28The Servlet Environment
Container
HTTP request
Servlet
HttpServletRequest
Browser
HttpServletResponse
HTTP response, containing HTML
Application server
DB
29A (small) Sample Servlet
- Purpose As part of handling a request, this
method checks to see if the session associated
with the request has stored a attribute
indicating that the session was authenticated. - public class SampleServlet extends HttpServlet
- implements Servlet
-
- public boolean isAuthenticated(
HttpServletRequest request ) -
- HttpSession session request.getSession(
false ) - if ( session null )
-
- return false
-
- String authenticationAttribute ( String )
session - .getAttribute( "authenticated" )
- return Boolean.parseBoolean(
authenticationAttribute ) -
30How to test the Servlet?
- Because this method asks for the containers
session parameters, direct JUnit test cases are
not possible in this environment. - The HttpServletRequest object is created by the
container and is only available there. The
request also must return a valid HttpSession to
check the attribute. - To test this code without the container, mock
objects for the HttpServletRequest and
HttpSession objects are needed.
31Servlet Testing approaches
- Three ways of running test cases for this servlet
will be shown here. - Out of container, using mock objects created by
EasyMock. - In container, using the Apache Tomcat server and
Cactus - In container, using the embeddable Jetty server
and Cactus.
32Out-of-container Strategy
Servlet
HttpServletRequest
JUnit Test Case
HttpServletResponse
Mock DB
33The sample servlet, again
- public class SampleServlet extends HttpServlet
- implements Servlet
-
- public boolean isAuthenticated(
HttpServletRequest request ) -
- HttpSession session request.getSession(
false ) - if ( session null )
-
- return false
-
- String authenticationAttribute ( String )
session - .getAttribute( "authenticated" )
- return Boolean.parseBoolean(
authenticationAttribute ) -
34Mock objects test case setup and teardown
- public class MockObjectTest
-
- private SampleServlet servlet
- private HttpServletRequest theRequest
- private HttpSession theSession
- _at_Before
- public void setUp( ) throws Exception
-
- servlet new SampleServlet( )
- theRequest
- EasyMock.createMock(HttpServletReques
t.class ) - theSession EasyMock.createMock(
HttpSession.class ) -
- _at_After
- public void tearDown( ) throws Exception
-
- EasyMock.verify( theRequest )
35Mock object test case test method
- _at_Test
- public void testIsAuthenticatedTrue( )
-
- EasyMock.expect( theRequest.getSession( false
) ) - .andReturn( theSession )
- EasyMock.expect( theSession.getAttribute("authe
nticated") ) - .andReturn("true")
- EasyMock.replay( theRequest )
- EasyMock.replay( theSession )
-
- boolean expected true
- boolean actual servlet.isAuthenticated(
theRequest ) - Assert.assertEquals( expected, actual )
-
36Running the test case
- All the previous test case needs to run is to
ensure the EasyMock class library is available. - Tests are run without using an application
server, and are run directly by JUnit. - Advantages
- Test is easy to set up and will run quickly
- Disadvantages
- The test is not running in the actual servlet
container, and we dont know if the container
will provide the correct request or not.
37In-container Strategy (1)
Container
Servlet
JUnit Test Case
Request
Response
Application server
Test DB
38In-container Strategy (2)
Container
Servlet
JUnit Test Case
Request
JUnit Test Proxy
Response
Application server
Test DB
39In-container testing with Cactus
- Cactus part of the Apache Jakarta project
- jakarta.apache.org/cactus
- Cactus is a framework to install a test component
inside an application server, and to communicate
with that test component. - Extension of the JUnit framework
- Result You can run JUnit tests from outside the
container, but have the tests executed inside the
container.
40How Cactus Works
- Cactus uses a proxy mechanism to run test cases
at the client, and redirect the requests to a
copy of the test case inside the server
container.
41In-container testing with Cactus
- What is required
- Include the JUnit and Cactus libraries as part of
the deployment to the application server
container. - Implement a client redirector that takes a JUnit
test case run outside the container, and
duplicate it within the container. - Implement a server redirector that lets Cactus
intercept incoming requests during test case
execution and provide them as objects to the test
case. - Provide a mechanism to get the test case results
back from the container to the test runner.
42Cactus JUnit test cases
- Test class must inherit from one of the following
classes - org.apache.cactus.ServletTestCase, to test a
servlet - org.apache.cactus.FilterTestCase, to test a
filter - org.apache.cactus.JspTestCase, to test a Java
server page
43Cactus JUnit test cases
- For a servlet test case, the JUnit test case will
have access to the following objects - request
- response
- config
- session
- The servlet context can also be accessed
indirectly.
44Structure of a Cactus test
- begin(), end() executed on the client side
before and after each test case - setUp(), tearDown() executed on the server side
before and after each test case - testXXX() a test method, to be executed within
the server container - beginXXX(WebRequest request) a method to be
executed on the client immediately before
testXXX(). - The parameter is used to set up the request to be
passed to the server. - endXXX(WebResponse theResponse ) a method to be
executed at the client immediately after
testXXX(). This is used to verify the HTTP
response from the user.
45Cactus Test Case Execution
- Here is the order of execution of the various
methods
46First In-Container Test Project
- Goal Execute a servlet test case on an Apache
Tomcat application server. - Create an Eclipse dynamic web project
- The target server is defined at project creation
time. - Eclipse will create the web.xml deployment
descriptor for the project. - Using the servlet wizard to create the servlet
will add the servlet to the deployment descriptor
47Additions to Deployment Descriptor
- Cactus redirectors must be added to the web.xml
file, so that they can be part of the project
deployment. - ltservletgt
- ltservlet-namegtServletRedirectorlt/servlet-namegt
- ltservlet-classgt
- org.apache.cactus.server.ServletTestRedirect
or - lt/servlet-classgt
- lt/servletgt
- ltservlet-mappinggt
- ltservlet-namegtServletRedirectorlt/servlet-namegt
- lturl-patterngt/ServletRedirectorlt/url-patterngt
- lt/servlet-mappinggt
48Test class, part 1
- public class TomcatCactusTest extends
ServletTestCase -
- private SampleServlet servlet
- _at_Before
- public void setUp( ) throws Exception
-
- servlet new SampleServlet( )
-
- _at_Test
- public void testIsAuthenticatedTrue( )
-
- session.setAttribute( "authenticated", "true"
) - boolean actual servlet.isAuthenticated(
request ) - boolean expected true
- assertEquals( expected, actual )
-
49Test class, part 2
- _at_Test
- public void testIsAuthenticatedFalse( )
-
- boolean actual servlet.isAuthenticated(
request ) - boolean expected false
- assertEquals( expected, actual )
-
- public void beginIsAuthenticatedNoSession(
- WebRequest theRequest )
-
- theRequest.setAutomaticSession( false )
-
- _at_Test
- public void testIsAuthenticatedNoSession( )
-
- boolean actual servlet.isAuthenticated(
request ) - boolean expected false
50Eclipse view of projectwith Cactus deployment
descriptor
51Libraries needed
- For the client
- junit.jar from JUnit
- servlet-api.jar from target Application Server
- cactus.jar
- aspectjrt.jar
- commons-httpclient.jar
- commons-logging.jar
- For the server
- junit.jar from JUnit
- Application server libraries
- cactus.jar
- aspectjrt.jar
- commons-logging.jar
52Libraries added to Eclipse projectfor Cactus
53Test run on Tomcat Server
54Cactus Jetty
- Cactus is used to run test cases inside the
container provided by Jetty. - Jetty can run the previous servlet test case that
was run on the Tomcat server. - Since Jetty is embedded, the server will be
started by the test case. - Cactus provides a wrapper to set up tests within
Jetty by doing a combined server start-up and
test case deployment.
55Cactus test wrapped for Jetty
- // The _at_RunWith line is needed for JUnit 4 to run
test suite - _at_RunWith( AllTests.class )
- public class JettyCactusTest
-
- public static Test suite( )
-
- // The next line adapts a JUnit 4 test for
a JUnit 3 runner - Test suite3 new JUnit4TestAdapter(
TomcatCactusTest.class ) - System.setProperty( "cactus.contextURL",
- "http//localhost8080
/test" ) - TestSuite suite new TestSuite( "All
tests with Jetty" ) - suite.addTest( suite3 )
- return new JettyTestSetup( suite )
-
-
56Test run on embedded Jetty server
57Part 3 End-to-end Testing
- Application server functions
- Receive HTTP request
- Look up and connect to database.
- Send query to database and obtain ResultSet
- Construct HTTP response
- Normally, a web page that includes data from the
ResultSet.
Client
Application server
Database
58The Servlet Environment
Container
HTTP request
Servlet
HttpServletRequest
Browser
HttpServletResponse
HTTP response, containing HTML
Application server
DB
59Test setup
Container
dbUnit
JUnit
Cactus
HTTP request
Servlet
HttpServletRequest
JUnit
HttpServletResponse
Application server Tomcat 6.0
httpUnit
HTTP response, containing HTML
MySQL 5.0 DB
60Resource Location on Servers
- To find resources (such as a database) on a
server, a naming service can be used to associate
a resource name with a specific location. - A directory service an extension of a naming
service can be used to locate resources based
on attributes. - To use the directory or naming service, an access
protocol must be used. - General example Lightweight Directory Access
Protocol (LDAP) - Java-specific example Java Naming Directory
Interface (JNDI)
61Data sources
- A generic interface DataSource can be used for
obtaining a connection to a resource - One implementation a JDBC connection
- JNDI returns a DataSource implementation object
as a result of locating a requested resource.
62Specifying an Application Server Resource
- Web application that specifies use of a resource
on a server, contained in META-INF/context.xml - lt?xml version"1.0" encoding"UTF-8"?gt
- ltContext path"/CustomerApp" docBase"CustomerApp"
- debug"5" reloadable"true"
crossContext"true"gt - ltResource name"jdbc/csi5118"
- auth"Container"
- type"javax.sql.DataSource"
- maxActive"100 "
- maxIdle"30"
- maxWait"10000"
- username"csi5118"
- password"(insert password)"
- driverClassName"com.mysql.jdbc.Driver
" - url"jdbcmysql//localhost3306/csi51
18"/gt - lt/Contextgt
63Code in Servlet to access JNDI resource
- public class MyJndiDataSource implements
DataSource -
- // The jndiName string is server-dependent.
This works for - // Apache Tomcat 6.0
- private String jndiName "javacomp/env/jdbc/c
si5118" - private DataSource dataSource
- public MyJndiDataSource( ) throws
NamingException -
- InitialContext context new
InitialContext() - dataSource (DataSource) context.lookup(
jndiName ) -
- public Connection getConnection( ) throws
SQLException -
- return dataSource.getConnection( )
-
-
64Testing Database Code in ServletUse Cactus for
In-Container Testing
Container
Cactus
dbUnit
JUnit
JUnit Test Case
Servlet
JUnit Test Proxy
Application server
Test DB
65In-Container Servlet-Database Testing
- Essentially, there is no difference between a
client-database test case, and a servlet-database
test case, except - JNDI is used to look up the data base at the
server. - The test case must inherit from ServletTestCase,
and be run by Cactus. - dbUnit can still be used for setting up the
database from the server.
66Constructing a web page response
- The simplest approach is to have the servlet
write HTML code to the responses output stream. - More advanced approach use Java Server Pages
(JSP), or the Java Server Faces (JSF) framework
67Example CustomerServlet
- protected void doGet( HttpServletRequest request,
- HttpServletResponse
response ) - throws ServletException, IOException
-
- // Get data from database
- ListltCustomergt customers getCustomerData(
request ) - // Create HTML response
- response.setContentType( "text/html" )
- PrintWriter pw response.getWriter( )
- pw.println( "lthtmlgtltheadgtlttitlegtCustomer
Listlt/titlegtlt/headgtltbodygt" ) - pw.println( "lth2gtCustomer listlt/h2gt" )
- pw.println( "lttable border\"1\"gt" )
- pw.println( "lttrgtltthgtIDlt/thgtltGiven
namelt/thgtltFamilynamelt/thgtlt/trgt" ) - for ( Customer aCustomer customers )
-
- pw.println( "lttrgt" )
- pw.println( "lttdgt" aCustomer.getId( )
"lt/tdgt" ) - pw.println( "lttdgt" aCustomer.getGivenName(
) "lt/tdgt" ) - pw.println( "lttdgt" aCustomer.getFamilyName
( ) "lt/tdgt" )
68Result from running Servlet
69HTML returned by Servlet
70Checking a Web Page
- The next step is to verify a response back at the
client, by ensuring that a web page with the
expected information is returned. - The HttpUnit tool can be integrated with Cactus
to check results returned by an application
server. - Open source tool at
- http//httpunit.sourceforge.net
- Be careful when using Cactus and HttpUnit
together, as they both have WebResponse classes - Cactus org.apache.cactus.WebResponse
- HttpUnit com.meterware.httpunit.WebResponse
71Items that can be located
- Links in web page
- A link has a click() method that can be used to
follow the link - Tables
- Determine number of rows and columns
- Obtain table contents by position
- Forms
- Locate form items, and set/check their values
- Click buttons
- Others
- Images
- Frames
- Cookies
- HTML elements
72Web response checking with Cactus
- Checking the web page response is done in the
method endXXX, where testXXX is the test method
that is run. - endXXX is run at the client, so it has the HTTP
response sent by the server.
73Test for doGet for Customer Servlet
- Aside from exceptions that could be thrown, there
is no output checking in the test case. - The response is verified by endDoGet()
- _at_Test
- public void testDoGet( ) throws Exception
-
- CustomerServlet servlet
- servlet new CustomerServlet( )
- servlet.doGet( request, response )
-
74Checking the Response with HttpUnit
- public void endDoGet( WebResponse theResponse )
- throws SAXException
-
- assertTrue( theResponse.isHTML( ) )
- int numTablesExpected 1
- int numTablesActual theResponse.getTables(
).length - assertEquals( numTablesExpected,
numTablesActual ) - WebTable theTable theResponse.getTables(
)0 - // Rows can be similarly checked
- int colsExpected 3
- int colsActual theTable.getColumnCount( )
- assertEquals( colsExpected, colsActual )
- // Example of checking a table cell
- String expected "ID"
- String actual theTable.getCellAsText( 0, 0
)
75Running the End-to-End Test
76Summary
- Tools needed to run the end-to-end test
- Eclipse Integrated Development Environment
- Apache Tomcat Java application server
- MySQL Database
- JUnit For running test cases
- Cactus In-container testing
- dbUnit For database test setup
- httpUnit For checking the web page