Creating a Unit-Testing Infrastructure


Before discussing techniques for unit testing a J2EE application, we ll provide a brief review on unit testing. Unit testing is the process of testing an individual software component or program to identify, isolate, and remove software deficiencies as early as possible in the development process. The developer responsible for programming the component is also responsible for unit testing. This yields a white box approach to testing that leverages the developer s knowledge of the component s inner workings to create tests exercising all critical paths through the code. The primary goal of unit testing is to detect errors, not prove that the code is error-free.

Tests should be repeatable and self-contained to the extent possible. Although the developer creates and initially executes the unit tests as part of the component-_development process, the same tests should be used as a form of regression testing whenever code changes may affect previously developed components .

The Importance of Unit Testing

The eXtreme Programming (XP) methodology advocates writing unit tests before writing the component itself as a means to clarify the expected behavior of the component and facilitate good design before coding begins. Although unit tests are valuable for XP and non-XP development efforts, by requiring unit tests before coding you are more likely to have complete unit tests at the end of the process. It is easy for developers to delay or eliminate the creation of unit tests when the schedule gets tight. Don t allow this to happen! Insist that unit tests be available and that the code successfully passes all unit tests before considering the work complete. The code review is a good time to review and sign off on these tests.

Best Practice  

Create unit tests for all business components in the system, and require all code to pass the tests before considering it complete. Use the code-review process to ensure compliance.

Why should you insist on a strong practice of unit testing in your development efforts? A few well-known advantages are these:

  • Bugs found and fixed early in the development process are much cheaper, in terms of time and money, than bugs discovered later in system or user testing.

  • A strong suite of unit tests makes design changes and other refactoring efforts less risky. You have the ability to regression-test everything affected by the change and ensure proper operation after a refactoring.

  • Maintainability is improved because the unit tests provide future developers with solid definitions of expected behaviors and the ability to regression test the application after making changes.

  • You ll sleep much better going into system and user-acceptance testing knowing that the candidate build has passed the entire suite of unit tests.

The problem isn t that developers and technical leads disagree with the value of unit testing ”they just find it burdensome and time-consuming to write and organize the tests. In other words, the cost of writing a detailed unit test often seems to outweigh the apparent benefits. That s where a unit-testing framework comes in to play by reducing the tedious work of creating, managing, and running unit tests. With a good framework the value of unit testing will always outweigh the cost.

JUnit Testing Framework

The Java development community has rallied around the open -source JUnit testing framework as a solid foundation for unit testing Java applications. JUnit provides a number of important classes and facilities required for unit testing:

  • The junit.framework.TestCase class provides support for pre-testing setup tasks , the unit tests themselves , asserting proper behaviors, and post-_testing teardown tasks. All unit test cases are developed as subclasses of the TestCase class in JUnit.

  • The junit.framework.TestSuite class represents a suite, or collection, of tests. A TestSuite may contain TestCase subclasses with unit tests, other TestSuite objects, or a combination of both. A typical hierarchical organization of TestSuite and TestCase objects is depicted in Figure 13.5.


    Figure 13.5:  Typical hierarchy of JUnit TestSuites and TestCases.

  • The junit.swingui.TestRunner utility provides a GUI-based environment for running and monitoring unit tests. A simple text-based approach is provided by the junit.textui.TestRunner class.

There are many additional framework components and extensions in JUnit, but this list represent the core classes and facilities used in the framework. Complete documentation and examples are available from the JUnit home page at http://www.junit.org .

Creating Tests for JUnit

JUnit is a powerful, flexible framework for unit testing, but it suffers from a number of weaknesses. The first is the tedious work required to create a TestCase subclass for each major component or subsystem in the application. Remember, the tests in the TestCase should test the component in a very thorough manner by invoking its methods with valid parameters, invalid parameters, and in any other way that exercises behaviors in the component. Creating a TestCase with this level of coverage may require many more tests than the number of methods in the tested component might imply.

The nature of good unit testing demands this type of complete, code-coverage testing of methods, parameters, and behaviors, and there is little that can be done to eliminate the work involved in writing these tests. Some IDE products provide a wizard or similar facility to create skeleton TestCase subclasses for a given Java component, but these simple skeletons would hardly be considered complete unit tests by our definition. The best approach in the absence of a perfect unit-test-creating tool is to mandate a certain level of test coverage, apply it consistently across the application components, and include the extra 15 to 20 percent effort required to create the tests in your development cost and time estimates. Don t cut corners and eliminate tests to meet the schedule.

Best Practice  

Mandate a consistent level of code coverage in your unit tests to maximize their value and bug-finding ability. Include realistic estimates of the required time and effort in your development estimates to avoid cutting corners to meet the schedule.

Using JUnit with J2EE Applications

The second weakness in the JUnit framework is the inability to test the full range of J2EE application components and architectures easily. J2EE is a distributed architecture, and JUnit is not. When a suite of tests is run in a JUnit TestRunner by a particular Java virtual machine, the tests must communicate with the tested components. The simplest form, direct invocation of component methods, is possible only when the tests and the tested components are collocated in the same Java virtual machine and loaded by the same classloader or by classloaders having a parent-child relationship, as in Figure 13.6.

click to expand
Figure 13.6:  Direct invocation of tested components.

Once you introduce distributed components, such as EJBs, the test classes must communicate with the components through the standard local and remote interfaces like any other client. As shown in Figure 13.7, remote interfaces offer the ability to run the tests in a separate classloader from the tested components. The tests could be located in a separate Java virtual machine, as shown, or in the application server instance.

click to expand
Figure 13.7:  Invoking components using remote interfaces.

EJBs that have only local interfaces require that tests be located in the same classloader or a child classloader. In other words, if your EJB components rely on local interfaces, as most EJB 2.0 entity beans do, it will be impossible to test them without running the test cases in the application server. The tests must, in fact, be run in the enterprise application itself to have the proper visibility to EJB classes and supporting classes.

Our recommended solution is to deploy the TestCase and TestSuite classes to the application server as part of the overall enterprise application and run them from that location. The logical place is in one of the Web applications in the enterprise application. Classes in the Web application have full visibility to EJB components and all supporting value and helper classes, and they may invoke the EJB methods through local interfaces. This is exactly what we need to be able to write unit tests against the EJB components.

Best Practice  

Deploy unit tests to a Web application in the enterprise application. The tests will have full visibility to EJB and support classes in the application and may access EJB components using local interfaces.

Deploying and Executing JUnit Tests

Getting the test classes deployed in the Web application is actually very easy. Recall that Web applications automatically deploy any archive files found in their WEB-INF/lib directory. If you compile all of the TestCase and TestSuite classes and package them in a .jar file in this directory, they will be deployed the next time the Web application starts.

The bigrez.com build script includes a dist-test target that packages the unit tests and some administration-site support classes in a junit-tests.jar archive file and places the archive file in the user Web application in the exploded enterprise application:

 <target name="dist-test" depends="compile">   <!-- create jar file with all test and admin support classes -->   <jar jarfile="${basedir}/junit-tests.jar">     <fileset dir="${build}"        includes="**/test/**/*.class, **/val/admin/*.class" />   </jar>   <!-- Copy testing jar file to user webapp WEB-INF/lib -->   <property name="test.lib.dir" value="${webapp.user.dir}/WEB-INF/lib"/>   <  copy file="${basedir}/junit-tests.jar" todir="${test.lib.dir}" /  >   <!-- Copy required support jars to webapp WEB-INF/lib -->   <copy file="${basedir}/lib/junit.jar" todir="${test.lib.dir}" />   <copy file="${basedir}/lib/httpunit.jar" todir="${test.lib.dir}" />   <copy file="${basedir}/lib/Tidy.jar" todir="${test.lib.dir}" />   <copy file="${basedir}/lib/js.jar" todir="${test.lib.dir}" /> </target> 

Note that we ve copied the main JUnit framework archive file ( junit.jar ) to the WEB-INF/lib directory, along with three archive files required for the HTTPUnit testing framework. We ll discuss the HTTPUnit framework later in this chapter. For now, simply recognize that placing the test cases and all required testing-framework archives in the WEB-INF/lib directory makes all of these classes available to the Web application after the next restart.

Executing the tests from within the Web application is straightforward; you simply run the tests from a presentation- tier component such as a JSP page or servlet. Although you cannot use the fancy GUI-based junit.swing.TestRunner to execute the tests in a Web application, the text-based version of TestRunner works fine when invoked from within a servlet or JSP page. Capturing and reporting the output requires a bit of code, but as Listing 13.1 shows, the JSP page that invokes the top-level TestSuite class for the bigrez.com application, ApplicationTests , is fairly simple.

Best Practice  

Run unit tests deployed in a Web application using a servlet or JSP page designed to capture the output of the text-based TestRunner and present the results in the output page.

Listing 13.1:  RunApplicationTests.jsp executes JUnit tests.
start example
 <%@ page import="com.bigrez.test.ApplicationTests, junit.framework.*,    junit.textui.*" %> <HTML> <HEAD><TITLE>Application Tests</TITLE></HEAD> <BODY> <H1>Test Results</H1> <%   ApplicationTests appTests = new ApplicationTests();   OutputStream output = new ByteArrayOutputStream();   TestRunner runner =      new TestRunner(new ResultPrinter(new PrintStream(output)));   runner.doRun(appTests.suite());   out.println("<pre><font size=-1>"+output+"</font></pre>"); %> </BODY> </HTML> 
end example
 

The output of this JSP is shown in Figure 13.8 for a series of successful tests. If a test fails the text version of TestRunner writes messages and exception traces to the OutputStream object that are then displayed on the page within the < pre > ..._ < /pre > tags.

click to expand
Figure 13.8:  Successful run of bigrez.com application tests.

The simple RunApplicationTests.jsp page invokes the doRun method on the TestRunner and passes in the top-most suite in the testing hierarchy. Although this is useful as a starting point, what you really need is the ability to view all of the available TestCase and TestSuite classes in the archive and be able to invoke single tests or selected suites of tests according to your needs. We ve got just the JSP for you ”it s called ApplicationTestDriver.jsp . This page uses the recursive suite() methods defined in TestSuite classes to traverse and display the entire contents of the test hierarchy starting with the topmost class. As shown in Figure 13.9, the ApplicationTestDriver.jsp page displays the names of the classes as well as the individual testXXX methods in each TestCase class in the tree display.

click to expand
Figure 13.9:  ApplicationTestDriver.jsp displays test hierarchy.

Clicking on a test or suite name causes the page to refresh and runs the desired test or suite much like the simple RunApplicationTests.jsp . You can run the entire suite, one branch of the suite, or a single test case in one of the tests simply by clicking on the appropriate link in the tree display. The output of the desired test is captured and placed at the bottom of the page as before. The source code for ApplicationTestDriver.jsp is available in the downloadable examples.

Organizing JUnit Tests

There are a variety of ways to organize the unit-testing classes for your application. One common scheme is a separate package structure in the working directory that mirrors the package structure of the tested components. If the tested component is located in src/java/com/bigrez/util , for example, the related TestCase subclass is located in the src/ test /com/bigrez/util directory. The build script must be modified to compile the test classes to a separate build directory to keep them from being included in the deployment archives. This technique works, but we prefer a simpler technique: a separate test package in the same src/java file structure.

The bigrez.com application uses the special package com.bigrez.test for all unit tests and test suites. Placing the tests in a separate package from the tested components allows the build process to exclude the tests in the normal build, package, and deployment process. The bigrez.com tests are organized into three major sections:

  • The SessionBeanTests suite contains tests associated with the stateless session beans in the application.

  • The EntityBeanTests suite contains tests that exercise the CMP entity beans.

  • The WebTests suite contains HTTPUnit tests used to test the behavior of JSP, servlet, and other presentation-tier components. HTTPUnit is covered in the next section.

In each major section, a TestCase subclass is created for each component (session bean, entity bean, and Web page) in the application. Listing 13.2 presents the ReservationSessionTest class as an example of a typical unit test.

Listing 13.2:  ReservationSessionTest.java.
start example
 package com.bigrez.test; import com.bigrez.ejb.*; import com.bigrez.utils.*; import com.bigrez.val.user.*; import junit.framework.*; import java.util.*; public class ReservationSessionTest extends TestCase {     private ReservationInfo createValidRezInfo()      {         ReservationInfo rezinfo = new ReservationInfo();         rezinfo.setGuestProfileId(1); // known value         rezinfo.setPropertyId(51); // known value         rezinfo.setRoomTypeId(11); // known value         Calendar today = Calendar.getInstance();         Calendar tomorrow = Calendar.getInstance();         tomorrow.add(Calendar.DAY_OF_MONTH,1);         rezinfo.setArriveDate(today.getTime());         rezinfo.setDepartDate(tomorrow.getTime());         rezinfo.setCardType("American Express");         rezinfo.setCardNum("370000000000002");         rezinfo.setCardExp("May 2005");         ReservationRateInfo rateinfo =              new ReservationRateInfo(today.getTime(),1,(float)99.0);         ArrayList rezrates = new ArrayList();         rezrates.add(rateinfo);         rezinfo.setRezRates(rezrates);         return rezinfo;     }     public void testCreateReservation()      {         ReservationLocal rez = null;         try {             ReservationInfo rezinfo = createValidRezInfo();             ReservationSessionLocal bean = (ReservationSessionLocal)               Locator.getSessionBean("ReservationSessionLocal");             rez = bean.createReservation(rezinfo);             assertTrue("Checking Property on rez = BigRez Inn",               rez.getProperty().getDescription().equals("BigRez Inn"));             assertTrue("Checking name on rez = Nyberg",               rez.getGuestProfile().getLastName().equals("Nyberg"));         }         catch (Exception e) {             fail(e.getMessage());         }     }     public static Test suite()      {         TestSuite suite = new TestSuite(ReservationSessionTest.class);         return suite;     } } 
end example
 

We created only a handful of unit tests for bigrez.com to provide some examples. An application of its size would normally require at least 100 to 150 individual test methods to test all session beans and entity beans thoroughly. The Web components should also be unit tested during development. Techniques for performing this testing are the subject of the next section.

Testing Web Components with HTTPUnit

Before we discuss the HTTPUnit testing framework, we need to clarify the scope of this discussion. We re talking here about unit testing during the development process, not system or acceptance testing of the completed application. Many commercial and open-source tools are available for scripting and executing Web component tests once the site has stabilized and is ready for system testing. These tools tend to be complex and, in the case of commercial tools, expensive. You need a simple tool or framework to create basic Web component unit tests that can be used during development in the same way JUnit tests are used with business components.

One such framework is the open-source HTTPUnit framework written by Russell Gold. HTTPUnit allows you to write unit tests that execute Web pages in your application and then verify the response using a set of classes and facilities provided by the framework. The HTTPUnit framework simulates many browser behaviors including form submission, JavaScript, basic HTTP authentication, cookies, and automatic page redirection. Responses may be interrogated and tested for correctness as text, through an XML DOM, or with methods designed to find links, forms, and other elements on the page.

Listing 13.3 presents a very basic example demonstrating the use of HTTPUnit to test the home page in bigrez.com .

Listing 13.3:  HomeTest.java performs simple Web tests.
start example
 package com.bigrez.test; import com.meterware.httpunit.*; import java.io.IOException; import java.net.MalformedURLException; import org.xml.sax.*; import org.w3c.dom.*; import junit.framework.*; public class HomeTest extends TestCase  {     /** Test the content of the Home page */     public void testHomePage() throws Exception      {         WebConversation conversation = new WebConversation();         WebRequest request =            new GetMethodWebRequest("http://localhost:7001/Home.page");         WebResponse response = conversation.getResponse(request);         String text = response.getText();         assertTrue("Title present in response",           contains(text,"<title>Welcome to BigRez.com!</title>"));     }     /** Test the behavior of the Property link on the Home page */     public void testRezInfoProperty() throws Exception      {         WebConversation conversation = new WebConversation();         WebRequest request =            new GetMethodWebRequest("http://localhost:7001/Home.page");         WebResponse response = conversation.getResponse(request);         WebLink propertyLink = response.getLinkWith("Choose Property");         assertNotNull("Property link present", propertyLink);         WebResponse response2 = propertyLink.click();         String text = response2.getText();         assertTrue("Form present in response",              contains(text,"Please enter the state or city"));     }     public static Test suite()      {         return new TestSuite( HomeTest.class );     }     private boolean contains(String text, String want)      {         return (text.indexOf(want) != -1);     } } 
end example
 

Note that the HomeTest class derives from the JUnit TestCase class, defines a suite method, and uses assertion methods just like a normal JUnit test class. It uses HTTPUnit classes such as WebRequest , WebResponse , and WebLink to invoke target pages and evaluate responses. See the HTTPUnit home page at http://www._httpunit.org for complete documentation and additional examples.

How useful is this type of testing in your overall development and unit-testing process? All unit testing is valuable, we believe, but the benefits must be weighed against the cost. In the case of standard unit tests, the benefits always outweigh the costs of creating and managing the test cases. The answer is not as clear with Web component unit tests. Writing HTTPUnit test classes and methods to test every link, form submission, and behavior in the Web site can be extremely tedious. Creating form data for submission requires a substantial number of method calls, and many pages respond properly only after many preliminary steps and pages required by the application. For example, the ReservationThankYou page in bigrez.com cannot be tested without walking through the entire reservation process.

We recommended that Web component unit testing be limited to a small number of tests, preferably one test for each page in the application. The resulting test suite can be used as a simple regression test when major changes are made to the application to ensure that all pages build properly (catching run-time JSP compile errors) and generate valid content. Testing critical navigation logic and presentation-tier functionality may also be worthwhile. Leave the complete testing of the Web site to the Quality Assurance team during system and user-acceptance testing.

Best Practice  

Create a limited number of HTTPUnit-based unit tests to verify the basic operation of Web components in the application. Use the resulting test suites for regression testing.




Mastering BEA WebLogic Server. Best Practices for Building and Deploying J2EE Applications
Mastering BEA WebLogic Server: Best Practices for Building and Deploying J2EE Applications
ISBN: 047128128X
EAN: 2147483647
Year: 2003
Pages: 125

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net