600 likes | 826 Views
Testing 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:
E N D
Testing 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
The 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. • What’s 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.
Application 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
The Database interface • The connection to a database via a programming language is via a connection protocol API (application programming interface): • ODBC: open database connectivity • JDBC: specific version for Java • Since databases are not compatible, there are normally specific drivers loaded in for each type of database (Oracle, MySQL, etc.) • Specify a URL such as jdbc:mysql://ste5007.site.uottawa.ca:3306/db to connect, and then provide a userid and password. • After logging in, SQL (structured query language) commands are issued to insert, view, update, or delete data. • Results from the database are loaded into a O/JDBC object for use.
For 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, version 1.4 or earlier (i.e. not Java EE 5) • Use of a web application server that conforms to the Java EE specification
Java 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.
Java Application Servers Container HTTP request Filter Servlet Enterprise JavaBean (EJB) Client / Browser Java Server Page (JSP) Tag Library HTTP response, containing HTML Application server DB
Server 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.
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.
Testing the client HTTP request Embedded Application Server Client / Browser JUnit Test Case HTTP response, containing HTML
Testing the client HTTP request Mock Connection Client / Browser JUnit Test Case HTTP response, containing HTML
HTTP 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...searchword=findme&option=search&submit=go File to retrieve File from which data was posted Form data
HTTP 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 ... <html> ... </html> • 404 Not found: the resource requested could not be located. Success Content information Web page in HTML
Potential 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.
Sample: Web client that returnsa string read from a web server public class WebClient { public String getContent( URL url ) { StringBuffer content = new StringBuffer(); try { HttpURLConnectionconnection = (HttpURLConnection) url.openConnection( ); connection.setDoInput( true ); InputStream is = connection.getInputStream( ); byte[] buffer = new byte[2048]; int count; while ( -1 != ( count = is.read( buffer ) ) ) { content.append( new String( buffer, 0, count ) ); } } catch ( IOException e ) return null; return content.toString( ); } }
Client 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.
Test strategy 1: Embedded server HTTP request Embedded Application Server Client / Browser JUnit Test Case HTTP response, containing HTML
Jetty • 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.
Setup for Embedded Jetty Server public class WebClientTest { private static HttpServer server; @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 TestGetContentOKHandler() ); server.addContext( context1 ); HttpContext context2 = new HttpContext(); context2.setContextPath( "/testGetContentNotFound" ); context2.addHandler( new NotFoundHandler() ); server.addContext( context2 ); server.start( ); } Handler for requests URL for the context will be http:localhost:8080/testGetContentOK
Jetty Handlers public class TestGetContentOKHandler extends AbstractHttpHandler { public void handle( String pathInContext, String pathParams, HttpRequest theRequest, HttpResponse theResponse ) throws HttpException, IOException { OutputStream out = theResponse.getOutputStream( ); ByteArrayISO8859Writer writer = new ByteArrayISO8859Writer( ); writer.write( "It works" ); writer.flush( ); theResponse.setIntField( HttpFields.__ContentLength, writer.size( ) ); writer.writeTo( out ); out.flush( ); theRequest.setHandled( true ); } }
Sample test + Teardown @Test public void testGetContentOK( ) throws MalformedURLException { WebClient client = new WebClient( ); URL url = new URL("http://localhost:8080/testGetContentOK"); String expected = "It works"; String actual = client.getContent( url ); assertEquals( expected, actual ); } @AfterClass public static void tearDownAfterClass( ) throws Exception { server.stop(); }
Strategy 2: No server HTTP request Stub Connection Client / Browser JUnit Test Case HTTP response, containing HTML
Strategy 2: Implementation HTTP request Client / Browser URL Stub URL Stream Handler Stub URL Connection JUnit Test Case HTTP response, containing HTML Stub Stream Handler Factory
Stub 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; }
Sample test + Setup @BeforeClass public static void setUpBeforeClass( ) throws Exception { URL.setURLStreamHandlerFactory( new StubStreamHandlerFactory() ); } @Test public void testGetContentOK( ) throws MalformedURLException { WebClient client = new WebClient( ); URL url = new URL("http://localhost:8080/testGetContentOK"); String expected = "It works"; String actual = client.getContent( url ); assertEquals( expected, actual ); }
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.
Java Application Servers (reprise) Container HTTP request Filter Servlet Enterprise JavaBean (EJB) Client / Browser Java Server Page (JSP) Tag Library HTTP response, containing HTML Application server DB
Servlets • 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 servlet’s 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 user’s browser.
The Servlet Environment Container HTTP request Servlet HttpServletRequest Browser HttpServletResponse HTTP response, containing HTML Application server DB
A (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 ) { HttpSessionsession = request.getSession( false ); if ( session == null ) { return false; } String authenticationAttribute = ( String ) session .getAttribute( "authenticated" ); return Boolean.parseBoolean( authenticationAttribute ); } }
How to test the Servlet? • Because this method asks for the container’s 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.
Servlet 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.
Out-of-container Strategy Servlet HttpServletRequest JUnit Test Case HttpServletResponse Mock DB
The sample servlet, again public class SampleServlet extends HttpServlet implements Servlet { public boolean isAuthenticated( HttpServletRequest request ) { HttpSessionsession = request.getSession( false ); if ( session == null ) { return false; } String authenticationAttribute = ( String ) session .getAttribute( "authenticated" ); return Boolean.parseBoolean( authenticationAttribute ); } }
Mock objects test case – setup and teardown public class MockObjectTest { private SampleServlet servlet; private HttpServletRequest theRequest; private HttpSession theSession; @Before public void setUp( ) throws Exception { servlet = new SampleServlet( ); theRequest = EasyMock.createMock(HttpServletRequest.class ); theSession = EasyMock.createMock( HttpSession.class ); } @After public void tearDown( ) throws Exception { EasyMock.verify( theRequest ); EasyMock.verify( theSession ); }
Mock object test case –test method @Test public void testIsAuthenticatedTrue( ) { EasyMock.expect( theRequest.getSession( false ) ) .andReturn( theSession ); EasyMock.expect( theSession.getAttribute("authenticated") ) .andReturn("true"); EasyMock.replay( theRequest ); EasyMock.replay( theSession ); boolean expected = true; boolean actual = servlet.isAuthenticated( theRequest ); Assert.assertEquals( expected, actual ); }
Running 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 don’t know if the container will provide the correct request or not.
In-container Strategy (1) Container JUnit Test Case Servlet Request Response Application server Test DB
In-container Strategy (2) Container JUnit Test Case Servlet Request JUnit Test Proxy Response Application server Test DB
In-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.
How 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.
In-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.
Cactus 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
Cactus 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.
Structure 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.
How Cactus Works • Here is the order of execution of the various methods:
First 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
Additions to Deployment Descriptor • Cactus redirectors must be added to the web.xml file, so that they can be part of the project deployment. <servlet> <servlet-name>ServletRedirector</servlet-name> <servlet-class> org.apache.cactus.server.ServletTestRedirector </servlet-class> </servlet> <servlet-mapping> <servlet-name>ServletRedirector</servlet-name> <url-pattern>/ServletRedirector</url-pattern> </servlet-mapping>
Test class, part 1 public class TomcatCactusTest extends ServletTestCase { private SampleServlet servlet; @Before public void setUp( ) throws Exception { servlet = new SampleServlet( ); } @Test public void testIsAuthenticatedTrue( ) { session.setAttribute( "authenticated", "true" ); boolean actual = servlet.isAuthenticated( request ); boolean expected = true; assertEquals( expected, actual ); }
Test class, part 2 @Test public void testIsAuthenticatedFalse( ) { boolean actual = servlet.isAuthenticated( request ); boolean expected = false; assertEquals( expected, actual ); } public void beginIsAuthenticatedNoSession( WebRequest theRequest ) { theRequest.setAutomaticSession( false ); } @Test public void testIsAuthenticatedNoSession( ) { boolean actual = servlet.isAuthenticated( request ); boolean expected = false; assertEquals( expected, actual ); }