Integrating unit tests with plugins SDK

Introduction #

This page describes one approach for integrating unit tests with the Liferay plugins SDK. The goal is to compile and run the JUnit tests for a portlet as part of the standard build process using the plugins SDK.

Overview of the approach:

  1. Update the folder structure for the portlet to house the test source and artifacts
  2. Override and update some of the ant targets in the portlet's build.xml

Project structure #

  • Add a new folder testroot at the same level as docroot that will contain all the test related source and artifacts (testroot is a sibling of docroot, not a child)
  • Add a new source folder testroot/test-src for the test sources
  • Add a new output folder testroot/test-classes for the compiled test classes
  • (Optional) Add a new folder testroot/test-reports for the test results generated by the <junit> ant task

Ant build file changes #

Add a dependency on Junit jar #

As an alternative to including the junit.jar (which is only needed for testing) in the portlet project, a maven dependency can be added using the maven ant tasks. Note: you will need to place the maven ant tasks jar file on the Ant classpath, visit the previous link to learn more.

The following snippet of the portlet's build.xml shows the updated project definition and the dependency declaration:

<project name="my-portlet" basedir="." default="deploy" xmlns:artifact="antlib:org.apache.maven.artifact.ant">

	<artifact:dependencies pathId="plugin-maven-test-lib.classpath">
		<dependency groupId="junit" artifactId="junit" version="3.8.1" scope="test" />
	</artifact:dependencies>

Override the compile target #

The compile target (which is inherited from build-common-plugin.xml) needs to be overridden to invoke the new test target (which is shown next).

This also goes in the portlet's build.xml:

	<target name="compile">
		<antcall target="merge" />

		<mkdir dir="docroot/WEB-INF/classes" />
		<mkdir dir="docroot/WEB-INF/lib" />

		<copy todir="docroot/WEB-INF/lib">
			<fileset dir="${app.server.lib.portal.dir}" includes="${plugin.jars}" />
		</copy>

		<copy todir="docroot/WEB-INF/tld">
			<fileset dir="${app.server.portal.dir}/WEB-INF/tld" includes="${plugin.tlds}" />
		</copy>

		<if>
			<available file="docroot/WEB-INF/src" />
			<then>
				<if>
					<available file="tmp" />
					<then>
						<path id="plugin-lib.classpath">
							<fileset dir="docroot/WEB-INF/lib" includes="*.jar" />
							<fileset dir="tmp/WEB-INF/lib" includes="*.jar" />
							<pathelement location="docroot/WEB-INF/classes" />
							<pathelement location="tmp/WEB-INF/classes" />
						</path>
					</then>
					<else>
						<path id="plugin-lib.classpath">
							<fileset dir="docroot/WEB-INF/lib" includes="*.jar" />
							<pathelement location="docroot/WEB-INF/classes" />
						</path>
					</else>
				</if>

				<copy todir="docroot/WEB-INF/lib">
					<fileset dir="${app.server.lib.portal.dir}" includes="${required.portal.jars}" />
				</copy>

				<if>
					<available file="docroot/WEB-INF/lib/portal-impl.jar" />
					<then>
						<fail>
	.

	Detected inclusion of portal-impl.jar in WEB-INF/lib.

	portal-impl.jar is designed with a large number of singleton classes which are
	instantiated on the basis that they will exist alone in the application server.

	While compile time issues may be resolved, portlets cannot be made to work by
	simply adding portal-impl.jar, because doing so violates the above assumption,
	and the resulting problems will be extremely difficult to debug.

	Please find a solution that does not require portal-impl.jar.
							</fail>
					</then>
				</if>

				<antcall target="compile-java">
					<param name="javac.classpathref" value="plugin.classpath" />
					<param name="javac.destdir" value="docroot/WEB-INF/classes" />
					<param name="javac.srcdir" value="docroot/WEB-INF/src" />
					<reference refid="plugin-lib.classpath" torefid="plugin-lib.classpath" />
				</antcall>
			</then>
		</if>

		<antcall target="test">
			<reference refid="plugin-lib.classpath" torefid="plugin-lib.classpath" />
		</antcall>
		
		<antcall target="merge" />
	</target>

Add a new test target #

Add the new test target which will compile and run the portlet's unit tests, then generate reports (to be used by a continuous integration tool perhaps).

Add the following to the portlet's build.xml:

	<!-- Compile and run unit tests -->
	<target name="test">

		<if>
			<available file="testroot/test-src" />
			<then>
				
				<delete dir="testroot/test-classes" />
				<delete dir="testroot/test-reports" />
				<mkdir dir="testroot/test-classes" />
				<mkdir dir="testroot/test-reports" />
			
				<path id="plugin-test-lib.classpath">
					<pathelement location="testroot/test-classes" />
					<path refid="plugin-maven-test-lib.classpath" />
					<path refid="plugin-lib.classpath" />
				</path>
			
				<!-- compile tests -->
				<antcall target="compile-java">
					<param name="javac.classpathref" value="plugin.classpath" />
					<param name="javac.destdir" value="testroot/test-classes" />
					<param name="javac.srcdir" value="testroot/test-src" />
					<reference refid="plugin-test-lib.classpath" torefid="plugin-lib.classpath" />
				</antcall>
				
				<junit fork="yes" forkmode="once" haltonfailure="yes">
	                <classpath>
	                	<path refid="plugin-test-lib.classpath" />
	                	<fileset dir="${app.server.lib.global.dir}" includes="*.jar" />
	                	<fileset dir="${app.server.lib.portal.dir}" includes="annotations.jar,commons-logging.jar,log4j.jar,util-bridges.jar,util-java.jar,util-taglib.jar" />
	                	<fileset dir="${project.dir}/lib" includes="activation.jar,jsp-api.jar,mail.jar,servlet-api.jar" />
	                </classpath>
				 	
				 	<batchtest todir="testroot/test-reports">
                    	<formatter type="xml"/>
                    	<fileset dir="testroot/test-classes">
                        	<include name="**/*Test.class"/>
                        	<exclude name="**/AllTests.class"/>
                        </fileset>
                    </batchtest>
	            </junit>

			</then>
		</if>

	</target>

Final notes #

  • The approach described above will automatically compile and run the tests every time the portlet is built/deployed using the plugins SDK.
  • The build will stop if the unit tests are not successful (change the value of the haltonfailure attribute if you wish to change that behavior).
  • The test source classes will not be bundled in the final war file.
0 Anhänge
13536 Angesehen
Durchschnitt (2 Stimmen)
Die durchschnittliche Bewertung ist 3.5 von max. 5 Sternen.
Kommentare
Antworten im Thread Autor Datum
Thanks it's very helpful, is there an example... Andrés Cerezo 15. April 2011 02:20
Hi Andrés, This isn't a core Liferay solution.... Ramy Georgy 15. April 2011 08:49
Maybe you could provide a Hello JUnit portlet... Steffen Schuler 22. August 2011 14:23
what does that mean? <antcall... Alexandr Basov 19. September 2014 03:57

Thanks it's very helpful, is there an example in the svn?

Cheers.
Gepostet am 15.04.11 02:20.
Hi Andrés,

This isn't a core Liferay solution. It's just what I did where I work, so it's not in any example plugin in svn.
Gepostet am 15.04.11 08:49 als Antwort auf Andrés Cerezo.
Maybe you could provide a Hello JUnit portlet and submit to the community. It seems like every one is creating a different solution in order to be able to write tests.
Gepostet am 22.08.11 14:23.
what does that mean?
<antcall target="compile-java">
maybe <antcall target="build-common-plugin.compile">
Thank you!
Gepostet am 19.09.14 03:57.