Besides at work, where I'm developing in .Net using Visual Studio, I'm a heavy
Eclipse user. I use it for a lot of things, programming
J2ME, J2EE, when developing Java desktop clients (using
Eclipse RCP) or even when writing
LaTeX documents. With its dozens of plugins it provides a very handy environment. This semester however, I switched to using Netbeans for my
project for the Advanced Web Programming course, mainly because Netbeans was the professor's choice. Anyway, it may never be bad to see some other environments too :)
Basic portlet project setupBut let's switch to the topic of this post. What I want to address in this post here is to give some instructions/hints on how to setup the environment for writing JUnit tests. The setup and configuration is mainly targeted to the project structure of Liferay portlets which are created as Free form projects in Netbeans. So first lets take a look at the project structure of the portlet project. The folders are arranged as follows:
- docroot
- WEB-INF
- classes (output for compiled java files)
- jsp
- lib (contains all of the jar libraries that are being used)
- src
- tld
- portlet.xml
- web.xml
- css
- ...
- nbproject (created by netbeans when you import the project)
- build.xml
So to not mix test cases with the source code that is compiled and deployed on the portal server, I decided to modify the structure like this
- docroot
- WEB-INF
- classes (output for compiled java files)
- jsp
- lib (contains all of the jar libraries that are being used)
- src
- tld
- portlet.xml
- web.xml
- css
- ...
- nbproject (created by netbeans when you import the project)
- unitTests
- lib
- output
- src
- (package structure: com/..../....)
- build.xml
Just some brief explanation on the choice of my folders (note, there may be better arrangements):
- lib
You may wonder why I created another "lib" folder, since I already have the one in the WEB-INF folder. Well that's because I thought it may be useful to do so, since there may be libraries specific to the unit testing as for instance mock libraries or the JUnit library itself.
Note however that your unit tests may also need the libraries from your WEB-INF/lib folder because you use of course classes from the WEB-INF/src folder which are the objects for your test cases. The libraries are however not copied, but just referenced in the classpath of your Ant scripts (we will see later)
- output
This folder contains the compiled test cases from the "unitTests/src" folder. Moreover it also needs to contain the compiled java src files from the WEB-INF/classes folder because you reference these objects in your test case (you actually test them there ;) )
- src
Finally, this folder contains the java packages containing your unit tests. I recommend names such as "..." and the test case name.
So, if you have a class MyService in the com.lifepin.services package and you create a test case, you would create an according package com.lifepin.unittests.services (in your unitTest folder) where you would put your MyServiceTest test case. (this is just a recommendation)
Let's go on. To acutally use your unitTests folder, you have to declare it as a source folder, actually more specific as a "Test package folder" (right-click on the project + Properties). Note this is specific to Netbeans and doesn't exist in Eclipse which makes no distinctions between test folders and normal src folders.
If you accidentally declare your unitTests folder as source folder you'll get the error message "No test root folder was found in the selected project"...
...when creating a new unit test case.
After you have done this, you should now see your folder
The next step, if you didn't do yet, is to download the JUnit library. You can get it from
here. Once downloaded, place it into the "unitTests/lib" folder.
Configuring Netbeans for unit testsLet's switch to the real stuff now, the configuration of your Netbeans IDE and your Ant build file. Before starting I strongly recommend you to backup your project. The best is if you zip your entire project folder s.t. you can quickly restore it if something went wrong.
The first thing to do is to
modify the original build file created by the Liferay portlet to handle the compilation and deploying of our unit tests as well as the copying of the compiled source files of our project into the unit test output folder ("unitTests/output").
If you created your portlet by using the Liferay-SDK's create scripts, you should have a very simple build.xml file:
<project name="portlet" basedir="." default="deploy">
<import file="../build-common-portlet.xml" />
</project>
In order to avoid typing always the same path names - which also adds bad redundancy - let's add some properties to the build.xml that reflect our project structure:
<project name="portlet" basedir="." default="deploy">
<import file="../build-common-portlet.xml" />
<property name="src.dir" value="${basedir}/docroot/WEB-INF/src"/>
<property name="classes.dir" value="${basedir}/docroot/WEB-INF/classes"/>
<property name="lib.dir" value="${basedir}/docroot/WEB-INF/lib"/>
<property name="test.src.dir" value="${basedir}/unitTests/src"/>
<property name="test.classes.dir" value="${basedir}/unitTests/output"/>
<property name="test.lib.dir" value="${basedir}/unitTests/lib"/>
</project>
Next we have to add the proper Ant targets for compiling our tests, for cleaning them and another Ant "path" element for identifying the unitTest's classpath. So at the end, the final build.xml should look as follows:
<project name="portlet" basedir="." default="deploy">
<import file="../build-common-portlet.xml" />
<property name="src.dir" value="${basedir}/docroot/WEB-INF/src"/>
<property name="classes.dir" value="${basedir}/docroot/WEB-INF/classes"/>
<property name="lib.dir" value="${basedir}/docroot/WEB-INF/lib"/>
<property name="test.src.dir" value="${basedir}/unitTests/src"/>
<property name="test.classes.dir" value="${basedir}/unitTests/output"/>
<property name="test.lib.dir" value="${basedir}/unitTests/lib"/>
<target name="compile-tests" depends="compile, clean-tests">
<mkdir dir="${test.classes.dir}"/>
<javac classpathref="test.class.path"
destdir="${test.classes.dir}"
srcdir="${test.src.dir}"
debug="true"
target="${ant.build.javac.target}">
<include name="**/*"/>
</javac>
<!--
compiled src files into the testing output directory since our tests will of course
reference them
-->
<copy todir="${test.classes.dir}">
<fileset dir="${classes.dir}"/>
</copy>
</target>
<target name="clean-tests">
<delete dir="${test.classes.dir}"/>
</target>
<path id="test.class.path">
<pathelement location="${classes.dir}"/>
<fileset dir="${lib.dir}">
<include name="*.jar"/>
</fileset>
<pathelement location="${test.classes.dir}"/>
<fileset dir="${test.lib.dir}">
<include name="*.jar"/>
</fileset>
</path>
</project>
The only probably not immediately obvious task is the "compile-tests" task. So what does it do? It basically first creates the output folder "unitTests/output" where the compiled unit tests will be placed. Then it uses the "javac" task to compile all of the unit tests. Finally it also copies the compiled source files of our project to the unitTest's output folder (note: the "compile-tests" depends on "compile", so it will be guaranteed that the source files will be compiled properly).
Unfortunately we're not yet finished. The next thing is to
adapt the Netbeans project.xml file. You may want to make another backup of your project folder to not loose the steps till here.
What we want to reach now is to be able to select a unit test case in the Netbean's project view, right-click on it and run it ("Run file" or "Shift+F6). To enable this a file called "ide-file-targets.xml" has to be created in the folder "nbproject", just beside the project.xml, with the following content:
<?xml version="1.0" encoding="UTF-8"?>
<project basedir=".." name="LifePin-IDE">
<import file="../build.xml"/>
<!-- runs the selected file in the unitTest source folder as JUnit test -->
<target name="run-selected-file-in-unitTests">
<fail unless="run.class">Must set property 'run.class'</fail>
<mkdir dir="${test.classes.dir}"/>
<junit dir="${test.classes.dir}" fork="true" printsummary="true" showoutput="true">
<classpath refid="test.class.path"/>
<formatter type="brief" usefile="false"/>
<formatter type="xml"/>
<test name="${run.class}"/>
</junit>
</target>
</project>
Then switch to the project.xml and exchange the "ide-actions" with the following:
<ide-actions>
<action name="build">
<target>compile</target>
<target>compile-tests</target>
</action>
<action name="clean">
<target>clean</target>
<target>clean-tests</target>
</action>
<action name="rebuild">
<target>clean</target>
<target>compile</target>
</action>
<action name="run.single">
<script>nbproject/ide-file-targets.xml</script>
<target>run-selected-file-in-unitTests</target>
<context>
<property>run.class</property>
<folder>unitTests/src</folder>
<pattern>\.java$</pattern>
<format>java-name</format>
<arity>
<one-file-only/>
</arity>
</context>
</action>
<action name="debug.single">
<script>nbproject/ide-file-targets.xml</script>
<target>debug-selected-file-in-unitTests</target>
<context>
<property>debug.class</property>
<folder>unitTests/src</folder>
<pattern>\.java$</pattern>
<format>java-name</format>
<arity>
<one-file-only/>
</arity>
</context>
</action>
</ide-actions>
Now as a last step we have to configure Netbeans to be able to build correctly. This has nothing to do with your build scripts, but just that Netbeans correctly recognizes the different source files and compiles when you type the code in the editor. Go to the project properties and reference your needed libraries as shown in the screenshot below (of course you'll have different jars). Note: save your project.xml before, since it will be modified by Netbeans when you change the settings here.
Oh, I forgot one thing: test debugging :) . That's really essential 'cause when a test fails you want to debug it and step through the code in order to find the root of your trouble. To do so there are two important steps to follow. First add the following target to your "ide-file-targets.xml":
<target name="debug-selected-file-in-unitTests">
<fail unless="debug.class">Must set property 'debug.class'</fail>
<nbjpdastart addressproperty="jpda.address" name="LifePin" transport="dt_socket">
<classpath refid="test.class.path"/>
</nbjpdastart>
<junit dir="${test.classes.dir}" fork="true" printsummary="true" showoutput="true">
<classpath refid="test.class.path"/>
<jvmarg value="-Xdebug"/>
<jvmarg value="-Xrunjdwp:transport=dt_socket,address=${jpda.address}"/>
<formatter type="brief" usefile="false"/>
<formatter type="xml"/>
<test name="${debug.class}"/>
</junit>
</target>
Then you have to correctly configure the output path of your unitTests test folder, otherwise the compiler will not stop:
Coding a test, creating a test suiteYou should now be ready to write your first unit test and run it. For convenience reasons I always usually create a test suite which groups the different test cases. Here's an example for JUnit 4:
@RunWith(Suite.class)
@Suite.SuiteClasses({AnotherTest.class, PinboardServiceTest.class})
public class TestSuiteAll {
@BeforeClass
public static void setUpClass() throws Exception {
}
@AfterClass
public static void tearDownClass() throws Exception {
}
@Before
public void setUp() throws Exception {
}
@After
public void tearDown() throws Exception {
}
}
You should get something like this:
So that concludes this tutorial. I hope I was able to help someone. Feel free to leave any comment if you have problems.
Questions? Thoughts? Hit me up
on Twitter