Developer: J2EE and OpenSource
by Julien Dubois
Today, unit testing is a very popular technique for ensuring code quality. Thanks to the JUnit framework, it has become quite easy to write unit tests for simple Java applications. However, where real-world enterprise applications are concerned, the typical JUnit testXXX()method will not be as helpful because those applications require that objects run inside a container.
In this article, I will describe the application of in-container testing in order to make JUnit tests access objects running inside a J2EE container. The example used here is a Struts-based Web application, which is fairly common among enterprise applications, but the techniques discussed are relevant to any J2EE project.
The best way to introduce JUnit is to show off some code, so let's start with a simple example, a JavaBean called "Message".
package jdubois.otn.cactus.domain; import java.io.Serializable; import java.util.Calendar; import java.util.Date; public class Message implements Serializable, Comparable { private Date postDate; private String user; private String comment; public Message(String user) { this.postDate = Calendar.getInstance().getTime(); this.user = user; } public void setUser(String user) { this.user = user; } public String getUser() { return user; } public void setComment(String comment) { this.comment = comment; } public String getComment() { return comment; } public Date getPostDate() { return postDate; } public int compareTo(Object o) { return this.postDate.compareTo(((Message) o).getPostDate()); } }
Here is a simple JUnit test, which checks the JavaBean constructor:
package jdubois.otn.cactus.test; import junit.framework.*; import jdubois.otn.cactus.domain.Message; public class MessageTester extends TestCase { public MessageTester(String sTestName) { super(sTestName); } public void testMessage() { Message message = new Message("user"); assertEquals("user", message.getUser()); assertNotNull(message.getPostDate()); } public static void main(String[] args) { junit.textui.TestRunner.run(MessageTester.class); } }
All there is to do with JUnit is create a testXXX()
method, run some code inside the method, and then check whether everything is all right with the various assertXXX(..)
methods. This is
quite simple to write but not really useful for testing today's J2EE applications. In fact, things get a lot more complicated as you enter the real enterprise applications space; the main problem is that, in J2EE land, most objects need to
run inside a container.
Let's write a simple Struts action:
package jdubois.otn.cactus.web; import java.util.TreeSet; import org.apache.struts.action.*; import javax.servlet.http.*; public class LoginAction extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws java.io.IOException, javax.servlet.ServletException { LoginForm frm = (LoginForm) form; request.getSession().setAttribute("user", frm.getName()); if (request.getSession().getServletContext().getAttribute("messages") == null) { request.getSession().getServletContext().setAttribute("messages", new TreeSet()); } return mapping.findForward("success"); } }
The execute(...)
method is really difficult to test. Creating an ActionForm is OK; but how is it possible, outside a container, to generate an ActionMapping, an HttpServletRequest, and an HttpServletResponse? The same
problem can occur with most objects in J2EE; they are not made to run outside a container, and it will be difficult to test them with a normal, container-free JUnit test.
There are two popular ways of dealing with this problem: mock objects and in-container testing.
Mock objects mock real-world J2EE objects. A framework such as MockObjects ( http://www.mockobjects.com) provides special implementations of some of the J2EE APIs, in order to use those objects from inside a JUnit test with no container running. For example, it provides a "MockHttpServletRequest" object.
In-container testing makes JUnit tests run inside a J2EE container. To achieve this goal, the J2EE application being tested sometimes needs to be slightly modified, depending on the tool used (Cactus and HttpUnit, in this document).
This article will focus on that second option, as it is easier to set up and perform a "real life" test. In-container testing requires less coding and adds a layer of integration testing that is most useful in practice. From this author's point of view, the current mock objects framework also lacks maturity and documentation. The downside of in-container testing is that tests will run more slowly, as the application needs to be deployed onto a J2EE serverwhich should not be a problem with OC4J, as it starts up rather quickly.
Sample application. For the purpose of this article, a sample application called "Discussion Board" will be created. It is a very simple discussion board where users can log in and post messages.
For simplicity's sake, this example does not use a persistent store; in other words, no database is being used. This is a simplification that will not occur in the real world. Most projects use an object-relational mapping toolsuch as CMP EJBs or TopLinkand as such, the focus of the testing is not necessarily the persistence layer.
Tools used. Running in-container tests requires some help from a few open source projects.
JUnit. Installing JUnit is a simple matter of launching JDeveloper, clicking on "help -> check for updates", and selecting JUnit from the available extensions. In order to use JUnit with Ant, copy the junit.jar file from <JDEVELOPER_INSTALL_DIR>\junit3.8.1 to your ant "/lib" directory.
Cactus. Cactus is an Apache Jakarta project, which can be found at http://jakarta.apache.org/cactus/index.html . The download page is located at http://jakarta.apache.org/cactus/downloads.html . For this article, the "jakarta-cactus-13-1.6.1.zip" file was used. Unzip this file into a directoryc:\java\cactus, for example.
StrutsTestCase. StrutsTestCase is a SourceForge project, which can be found at http://strutstestcase.sourceforge.net . For this article, the "strutstest210-1.1_2.3.zip" file was used. Unzip this file into a directoryc:\java\strutstest, for example.
HttpUnit. HttpUnit is another SourceForge project, which lives at http://httpunit.sourceforge.net . For this article, version 1.5.4 was used. Unzip "httpunit-1.5.4.zip" into a directory next to StrutsTestCasec:\java\httpunit, for example.
Important note: Cactus and HttpUnit do not play well together, as they both use the NekoHtml library. As they cover the same kind of functionality, it is expected that you won't try to use both of them at the same time. However, if you wish to do so from within JDeveloper, you will have to use NekoHtml with HttpUnit (and not JTidy), and you will have to build a single JDeveloper library containing all the Cactus and HttpUnit jars. If you do run into a java.lang.reflect.InvocationTargetException, it stems from the above problem. The other libraries needed for the projectsuch as JUnit runtime, JSP runtime, Struts runtime, and Servlet runtimewill be automatically added by JDeveloper during the creation of the sample application.
The sample application will be a Struts-based discussion board. Create a new application in JDeveloper, and right-click on the project: select new -> Web tier -> Struts -> Struts Contruller Page Flow.
The page flow in Figure 1 will be used:
Figure 1: Sample Page Flow for a Struts-based Discussion Board
<?xml version = '1.0' encoding = 'windows-1252'?> <!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd"> <struts-config> <form-beans> <form-bean name="loginForm" type="jdubois.otn.cactus.web.LoginForm"/> <form-bean name="messageForm" type="jdubois.otn.cactus.web.MessageForm"/> </form-beans> <action-mappings> <action name="loginForm" path="/Login" type="jdubois.otn.cactus.web.LoginAction" unknown="true" input="/login.jsp"> <forward name="success" path="/messages.jsp" redirect="true"/> </action> <action name="messageForm" path="/AddMessage" type="jdubois.otn.cactus.web.AddMessageAction" unknown="false"> <forward name="success" path="/messages.jsp"/> <forward name="login" path="/login.jsp"/> </action> </action-mappings> <message-resources parameter="jdubois.otn.cactus.web.ApplicationResources"/> </struts-config>
In order to generate the page flow diagram, copy/paste the above code in the struts-config.xml file, go to the "diagram" tab, and refresh it (right-click -> diagram -> refresh diagram from struts-config).
testXXX()
methods, such as the one described in the introduction. Copy and paste the testMessage()
method:
public void testMessage() { Message message = new Message("user"); assertEquals("user", message.getUser()); assertNotNull(message.getPostDate()); }
main(...)
method can be added:
public static void main(String[] args) { junit.textui.TestRunner.run(MessageTester.class); }
When this method is added, JDeveloper recognizes the JUnit test, and right-clicking on the class brings a "Run -> As a JUnit Test Case" menu.
What's interesting is, of course, when a test fails, not when it is successful. Let's make the previous test fail by adding in the middle of the method:
assertTrue(false);
This code will always fail, and JUnit will diagnose a failure. When this kind of problem occurs (and we do hope they will occurafter all, it's why we're creating tests), there are two useful helpers:
Test suites are groups of tests. In JDeveloper, they are especially convenient:
Here is how to get started with a simple test suite:
Figure 2: Running a Test Suite
A quick glance at the generated source code for the Test Suite shows that it is very easy to add new Test Cases to the Test Suite:
suite.addTestSuite(jdubois.otn.cactus.test.MessageTester.class);
Now that a simple JUnit test has been realized, it's time to move on to the real thing: J2EE application testing.
Before running any Cactus in-container J2EE tests, the application needs to be "cactified." This is just a matter of modifying web.xml and adding a properties file. This "cactification" can be done manually or by using an Ant script.
Manual cactification. Cactus uses a special servlet in order to run unit tests on the container from a standard JUnit test runner.
A new servlet, and its associated mapping, needs to be added to the web.xml file:
<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>
The cactus test runner needs a properties file, called cactus.properties, in the classpath. Right-click on the "Application Sources" tree, add a new file, and create "cactus.properties" at the root of the source tree. This file needs three properties:
cactus.contextURL = http://localhost:8988/user-Project-context-root cactus.servletRedirectorName=ServletRedirector cactus.enableLogging=true
The "user-Project-context-root" is the deployment context of the current application. If you are unsure of the name of this context, right-click on the "login.jsp" file and select "run": in the newly opened browser, the URL to the current application will be visible.
The <war/> and <cactifywar/> Ant tasks. The <war/> task is a standard Ant task for creating war .
<!-- create the war file --> <war destfile="./discussion-board.war" webxml="public_html/WEB-INF/web.xml"> <fileset dir="public_html"> <exclude name="WEB-INF/web.xml"/> </fileset> <lib dir="${jdev.home}/jakarta-struts/lib/*"> </lib> <classes dir="classes"/> </war>
More documentation and examples are available on Ant's Web site: http://ant.apache.org .
The Cactus distribution provides a number of additional tasks for Ant. The most important one is the <cactifywar/> task.
The <cactifywar/> task analyzes a normal war file, and transforms it into a cactified war. It automates the manual steps we performed in the previous section. The only minor drawback is that it is a bit slow to run, as it keeps unzipping/cactifying/zipping the war file. It also allows keeping a standard, non-cactified war file for deployment. Important to note is that the ServletRedirector servlet should not be bundled in a production war, as it can be a potential security breach.
From the Cactus Web site ( http://jakarta.apache.org/cactus/integration/ant/index.html ), the Cactus Ant integration is fairly simple:
<!-- Define the Cactus tasks --> <!-- If you generate the build.xml file with JDeveloper, all the Cactus files should already be in the classpath, so there is no need to redefine it --> <taskdef resource="cactus.tasks"> <classpath refid="classpath"/> </taskdef> <!-- Cactify the web-app archive --> <cactifywar srcfile="./discussion-board.war" destfile="="./cactified-discussion-board.war"> <servletredirector/> <lib file="<STRUTSTESTCASE_INSTALL_DIR>/strutstest-2.1.0.jar"/> </cactifywar>
If you have problems using Ant and Cactus, the first thing to do is to copy the junit.jar file into the ant "/lib" directory, as explained in the "Installing JUnit" section of this article. Cactus provides other Ant tasks to automate the testing process; they are covered at the end of this article, in the " Next Steps " section.
Creating the action. The LoginAction uses a FormBean and two JSPs (login.jsp and messages.jsp). Let's create those three objects.
An easy way to create those files is to open the Struts Page Flow:
package jdubois.otn.cactus.web; import java.util.TreeSet; import javax.servlet.http.*; import javax.servlet.ServletContext; import org.apache.struts.action.*; public class LoginAction extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws java.io.IOException, javax.servlet.ServletException { LoginForm frm = (LoginForm) form; request.getSession().setAttribute("user", frm.getName()); ServletContext context = request.getSession().getServletContext(); if (context.getAttribute("messages") == null) { context.setAttribute("messages", new TreeSet()); } return mapping.findForward("success"); } }
package jdubois.otn.cactus.web; import org.apache.struts.action.*; import javax.servlet.http.HttpServletRequest; public class LoginForm extends ActionForm { private String name; public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) { ActionErrors errors = new ActionErrors(); if (this.name == null || this.name.equals("")) { errors.add("error", new ActionError("user.name.required")); } return errors; } public void setName(String name) { this.name = name; } public String getName() { return name; } }
Note that the ActionError uses the "user.name.required" key, specified in Struts' ApplicationResources.properties file:
user.name.required=A user name is required
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html"%> <%@ page contentType="text/html;charset=windows-1252"%> <html> <head> <meta http-equiv="Content-Type" CONTENT="text/html;charset=utf-8"> <title>Welcome</title> </head> <body> <div align="center"> <html:form action="/Login"> <h2>Welcome to our online discussion board</h2> <html:errors/> <p>Please enter your name : <html:text property="name" size="20"/> <html:submit value="Login"/> </p> </html:form> </div> </body> </html>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html"%> <%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean"%> <%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic"%> <%@ page contentType="text/html;charset=windows-1252"%> <html> <head> <meta http-equiv="Content-Type" CONTENT="text/html;charset=utf-8"> <title>Discussion</title> </head> <body> <div align="center"> <table border="1"> <tr> <th>User</th> <th>Message</th> </tr> <logic:iterate id="message" name="messages"> <tr> <td> <bean:write name="message" property="user"/> </td> <td> <bean:write name="message" property="comment"/> </td> </tr> </logic:iterate> </table> </div> </body> </html>
Right-click on "login.jsp", and select "run". A browser Window will open, and it will be possible to use the LoginAction (see Figure 3):
Figure 3: Discussion Board Login Window
Testing the action with StrutsTestCase. Start your testing by asking yourself the question, "what do we want to test?"
It would be helpful to get these questions answered by the future users of the application, to help define application flow.
Now let's test the LoginAction. Select the "Application Sources", and create a new class.
package jdubois.otn.cactus.test; import servletunit.struts.CactusStrutsTestCase; public class LoginActionTester extends CactusStrutsTestCase { public LoginActionTester(String testName) { super(testName); } public void testSuccessfulLogin() { addRequestParameter("name","Julien"); setRequestPathInfo("/Login"); actionPerform(); verifyForward("success"); verifyForwardPath("/messages.jsp"); assertEquals("Julien", getSession().getAttribute("user")); verifyNoActionErrors(); } public void testFailedLogin() { setRequestPathInfo("/Login"); actionPerform(); verifyInputForward(); verifyForwardPath("/login.jsp"); verifyActionErrors(new String[] {"user.name.required"}); assertNull(getSession().getAttribute("user")); } public static void main(String[] args) { junit.textui.TestRunner.run(LoginActionTester.class); } }
This test case is a CactusStrutsTestCase, which is an object provided by StrutsTestCase (which was installed earlier). It uses both JUnit and Cactus to test Struts actions.
In this case, the testSuccessfulLogin()
method:
And the testFailedLogin()
method:
To run this test case:
The end result is seen in the JDeveloper consule, as with the previous example of a simple JUnit test.
Let's add the Test Case to the AllTests Test suite that was previously created:
suite.addTestSuite(jdubois.otn.cactus.test.LoginActionTester.class);
Relaunch OC4J, and run "AllTest". Figure 4 is the end result:
Figure 4: TestingYour Code
This example mixed some JUnit methods (such as assertTrue()
) and some StrutsTestCase methods (such as verifyForward()
). As StrutsTestCase is an extension of JUnit, everything that works
with JUnit also works for J2EE unit tests.
The methods used for testing the LoginAction are pretty simple, but they are enough to get us started. They provide enough functionality to verify that a Struts action performs correctly.
Let's test again a successful login, this time using HttpUnit. As explained in the installation part of this article, HttpUnit does not play well with Cactus. The best way to write this test is to temporarily remove the Cactus libraries from your Oracle JDeveloper project, in order to avoid any classloading errors.
HttpUnit is not a JUnit extension per se, as Cactus is. In a sense, its name is rather misleading, as all it provides is a set of classes that can act as a browser: they handle forms, tables, even JavaScript and cookies. But those classes, when used with JUnit, can become a powerful black-box testing mechanism, as can be seen in the next example, where HttpUnit is used within a standard JUnit test case:
package jdubois.otn.cactus.test; import junit.framework.*; import com.meterware.httpunit.*; public class LoginActionHttpTester extends TestCase { public LoginActionHttpTester(String sTestName) { super(sTestName); } public void testSuccessfulLogin() throws Exception { WebConversation wc = new WebConversation(); WebRequest req = new GetMethodWebRequest("http://127.0.0.1:8988/your-project-context-root/login.jsp"); WebResponse resp = wc.getResponse(req); assertTrue(resp.getText().indexOf("Welcome to our online discussion board") > 0); WebForm form = resp.getFormWithName("loginForm"); form.setParameter("name", "Julien"); form.submit(); resp = wc.getCurrentPage(); assertEquals("Discussion", resp.getTitle()); } public static void main(String[] args) { junit.textui.TestRunner.run(MessageTester.class); } }
This code is a standard JUnit test case, which can be added to a test suite as any normal test case. Of course, HttpUnit will work only if the Web application is correctly deployed on the application server. The best way to achieve this feature is to keep an instance of the application server running and hot-redeploy the tested applications at runtime, before running the JUnit tests. With JBoss, this means copying the generated war/ear to the deploy/directory and checking whether the login.jsp page is available. One can argue that hot-redeploying all the time can wreak havoc on an application server, but in this author's experience you can do this hundreds of times before hurting an application servertoday's hot-deployment techniques are sufficiently mature to cope with this matter.
As we will see later, this kind of problem does not exist with Cactus, which provides its own Ant task for dealing with this problem. In the future Cactus' author, Vincent Massul, will probably release a generic framework for starting/stopping application servers, which will come in handy for using HttpUnit.
Choosing between Cactus and HttpUnit. As we have seen, the same kind of tests can be provided by Cactus and HttpUnit. HttpUnit is more a black-box testing tool. It is a language-agnostic testing tool (you could test PHP pages with it, for example). It is also easier to understand and use for testers. But as long as you want to use J2EE technulogy, and especially Struts actions, Cactus is the better tool. For example, StrutsTestCase's treatment of ActionErrors is extremely efficient. It will also require a lot less maintenance, as tests will not depend on the HTML presentation layer.
The LoginAction was developed in a very classic way: first the developer codes the application, and then the tester writes JUnit tests. Today, a different approach exists: Extreme Programming (XP) and test-first development.
In XP, the developer writes the tests before coding anything else. This is a very modern and popular technique, and it is very customer-focused. The best way to get started with XP is probably to read Kent Beck's excellent "Extreme Programming Explained," but many other books have been published on this subject, and the XP community provides a lot of documentation on the internet.
So let's start with the usual question: What do we want to do?
Here is the code of the JUnit test case:
package jdubois.otn.cactus.test; import java.util.Collection; import servletunit.struts.CactusStrutsTestCase; public class AddMessageActionTester extends CactusStrutsTestCase { public AddMessageActionTester(String testName) { super(testName); } public void testAddMessage() { request.getSession().setAttribute("user", "Julien"); addRequestParameter("comment","First post!"); setRequestPathInfo("/AddMessage"); actionPerform(); verifyForward("success"); verifyForwardPath("/messages.jsp"); assertEquals("Julien", getSession().getAttribute("user")); Object messages = request.getSession() .getServletContext().getAttribute("messages"); assertNotNull(messages); if(!(messages instanceof Collection)) { fail("Messages should be a Collection."); } verifyNoActionErrors(); } public void testAddMessageNoUser() { addRequestParameter("comment","First post!"); setRequestPathInfo("/AddMessage"); actionPerform(); verifyForward("login"); verifyForwardPath("/login.jsp"); verifyNoActionErrors(); } public static void main(String[] args) { junit.textui.TestRunner.run(AddMessageActionTester.class); } }
Add this test case to the "AllTests" Test Suite, restart OC4J, and launch the Test Suite.
The whule application compiles and deploys; however, the new tests fail as expected. So it is possible to create test cases even before coding a single action.
This technique can be used for large scale; in this way, JUnit is used as a light project management tool. JUnit can produce reports that give an indication of the project's state. For example, in our case we have two failures out of five tests, so our project isvery roughlyat 60-percent completion.
Let's finish our work and code the AddMessageAction:
package jdubois.otn.cactus.web; import java.util.Collection; import javax.servlet.http.*; import org.apache.struts.action.*; import jdubois.otn.cactus.domain.Message; public class AddMessageAction extends Action { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws java.io.IOException, javax.servlet.ServletException { if (request.getSession().getAttribute("user") == null || request.getSession().getServletContext().getAttribute("messages") == null) { return mapping.findForward("login"); } String user = (String) request.getSession().getAttribute("user"); MessageForm frm = (MessageForm) form; if (frm.getComment() != null && !frm.getComment().equals("")) { Message message = new Message(user); message.setComment(frm.getComment()); Collection messages = (Collection) request.getSession() .getServletContext().getAttribute("messages"); messages.add(message); } frm.setComment(""); return mapping.findForward("success"); } }
MessageForm:
package jdubois.otn.cactus.web; import org.apache.struts.action.*; import javax.servlet.http.HttpServletRequest; public class MessageForm extends ActionForm { private String user; private String comment; public void setUser(String user) { this.user = user; } public String getUser() { return user; } public void setComment(String comment) { this.comment = comment; } public String getComment() { return comment; } }
The action needs to be called from a JSP page, so this must be added to messages.jsp:
<html:form action="/AddMessage"> <P>User : <bean:write name="user"/> </P> <P> <html:textarea property="comment"/> </P> <P> <html:submit value="Add a new message"/> </P> </html:form>
Now, right-click on login.jsp, and select "run". The application should work completely, and you can invite some fellow developers to talk with you on the discussion board (see Figure 5).
Figure 5: Discussion Board Application Snapshot
Congratulations! The whule test suite should now succeed, as all five JUnit tests are successful. Remember this handy saying, "Keep the bar green to keep the code clean."
Here are some excellent sources of further information on this topic:
The problem with Struts, and testing EJBs. Struts actions should be used only as glue between the presentation layer and the business layer. A common mistake is to include business logic in Struts actions. Putting business logic into Session Bean EJBs allows for better reusability of the code and looser coupling between the different layers.
If the business logic is pushed away from Struts actions, then:
Testing Session Beans with JUnit is in fact very simple, thanks to their remote interface. JUnit can use Session Beans as regular Java objects, so no supplementary framework is needed in order to invoke them. The code would look like this
package jdubois.otn.ejb.test; import javax.naming.InitialContext; import javax.rmi.PortableRemoteObject; import junit.framework.TestCase; public class SimpleEjbTest extends TestCase { protected SimpleEjbHome ejbHome; protected SimpleEjb simpleEjb; private String name = "ejb/remote/simplebean"; public SimpleEjbTest() throws Exception { InitialContext context = new InitialContext(System.getProperties()); Object obj = context.lookup(name); ejbHome = (SimpleEjbHome)PortableRemoteObject. narrow( obj, SimpleEjbHome.class); simpleEjb = ejbHome.create(); } public void testSimpleMethod () { try{ String result = simpleEjb.sayHello(); assertEquals("Hello", result); } catch (Exception e){ fail("An exception has occured " + e.getClass() + " : "+e.getMessage()); } } }
This code can be used directly inside JUnit, exactly as in the first example (the Message JavaBean).
Debugging an application with JDeveloper. When a test case fails, using a debugger can help to find what the problem is. The debugger provided with Oracle JDeveloper works perfectly with the current JUnit approach, even when testing server-side components.
In order to use the debugger, choose one line of a Struts action, where the debugging should start. Click on the left of the line; a small red dot should appear. Then right-click on "login.jsp", and select "debug" instead of "run". If the JUnit tests are launched (using the AllTests.java test suite, with the JUnit GUI for example), the test will halt and JDeveloper will go into debug mode at the selected break point (see Figure 6):
Use the following resources to learn more about Ant and start building and deploying Java projects.
Ant Industry Trends A behind-the-scenes look into the making of this cross-platform building tool.
Read more about Ant Integration in JDeveloper Ant integration within Oracle JDeveloper is done by adding an Ant build file to a JDeveloper project or by generating a new Ant build file from an existing JDeveloper project.
Download Oracle JDeveloper 10g Oracle JDeveloper 10g is an integrated development environment with end-to-end support for modeling, developing, debugging, optimizing, and deploying Java applications and Web services.
Testdrive: Using Ant for Builds This viewlet demonstrates how users with existing Ant projects can use these projects within JDeveloper.
Getting started with Ant Part 1 Here's to begin using this invaluable tool for building and deploying Java projects. This article coveres some basic Ant tasks that you would likely perform during the Java development process.
Getting started with Ant Part 2 Here's Part 2 in our series on an invaluable tool for building and deploying Java projects. This article discusses some of Ant's more advanced features that are available in Ant's two task packages: the core task package and the optional task package.
Command-line Approach to Creating Java Applications on Linux This article serves as a good hands-on guide to developing and deploying Java client applications on Linux using Ant.
Read more about Ant Visit the official Apache Ant site for more project details.
Ant for Task Writers Take advantage of the changes in Ant 1.6 internals to write a task or even a library of tasks.
Related Articles and Downloads
Blog: Ant Deployment to OC4J
Blog: How to dynamically retrieve properties from a JDeveloper project file into an ant build file?
Viewlet: Using CVS for Software Configuration
Figure 6: Debugging with Oracle JDeveloper and JUnit
Oracle JDeveloper IDE uses the integrated OC4J application server and is the easiest way to test and debug an application within JDeveloper. JDeveloper uses Java's standard debugging abilities to achieve this feature, and as such it can debug any standard Java application server. For example, here is a how-to on debugging an application deployed on the JBoss application server from within JDeveloper: .
The <cactus/> task. The <cactus/> ant task is able to launch an application server and execute the Cactus test case against it, automatically. It is one of the essential tasks to use if you want to set up Continuous Integration of your projects.
Unfortunately, OC4J is not one of the application servers available in the <cactus/> task, but as long as a standard war/ear file is coded, another application server can be used for this purpose. It is possible, according to the Cactus documentation (http://jakarta.apache.org/cactus/integration/ant/task_cactus.html#generic ), to use OC4J with the <cactus/> task. However, OC4J is not a supported platform and, in the author's experience, you might run into problems while stopping OC4Jwhich will wreak havoc in a Continuous Integration process.
<mkdir dir="results"/> <cactus warfile="./cactified-discussion-board.war" fork="yes" failureproperty="test.failure"> <classpath refid="classpath"/> <containerset timeout="300000"> <jboss3x dir="${jboss.install.dir}" port="8080"/> </containerset> <formatter type="brief" usefile="false"/> <batchtest todir="results"> <fileset dir="${src.dir}"> <include name=" jdubois/otn/cactus/test/AddMessageActionTester.java"/> <include name=" jdubois/otn/cactus/test/LoginActionTester.java"/> </fileset> </batchtest> </cactus> <fail message="Cactus in-container tests failed." if="test.failure" />
The <junitreport/> task. <junitreport/> is a standard Ant task that creates an HTML report of JUnit test results.
Configure JUnit to generate an XML report of the test. For this:
<junitreport todir="results"> <fileset dir="results"> <include name="TEST-*.xml"/> </fileset> <report todir="results" format="frames"/> </junitreport>
If you use Continuous Integration on a dedicated machine, you can create a complete HTML report of all JUnit tests, and configure your Web server so that all team members can have access to the complete set of tests results.
Setting up "Continuous Integration." Continuous Integration is part of the Extreme Programming method. With Continuous Integration, tests are run automatically, several times a day. On the author's current project, tests are run every hour. If a bug is found, an error report is sent by e-mail to the members of the team who committed code during that period. One good practice is that when someone breaks down the Continuous Integration, his most urgent task is to correct his code and fix the build.
Continuous Integration can do more than just run JUnit tests. For instance, it can run Checkstyle tests to ensure that all developers are following the coding guidelines of the project (another Extreme Programming good practice). Checkstyle is a SourceForge project:http://checkstyle.sourceforge.net/ .
There are several Continuous Integration tools available:
The author has been using CruiseControl for a long time and heartily recommends it. It's a wonderful toolvery well thought out and easy to set up.
JUnit, server-side testing, test-first development. . . . These are very complex matters, but today a number of tools are available to help developers get past the technical difficulties. Besides, these tools integrate well with Oracle technologies such as JDeveloper and OC4J.
So why not start testing now? It gives you confidence in your application, it gives you the courage to change your code, and it is fun to do .
Julien Dubois is a J2EE expert living in Paris, France. He can be found on the internet at http://www.julien-dubois.com .