Q: I'm working several projects and would like to debug or extend their test suites, but the project tests are written using TestNG, SuiteRunner, or another alternative to JUnit. The NetBeans test running tools for Standard projects appear to be only for JUnit tests. Can NetBeans still be used to debug and develop the project test suites?
A: Yes it can, with a little Ant build-file customization. If you can write an Ant build-file
<target>
to run your project tests, and one to run an individual test suite class, you can customize your build-file to tell the NetBeans IDE to invoke these targets without writing any NetBeans modules, or even any Java code.
Learn to customize a NetBeans "standard project" build-file so you can run and debug a project test suite that uses TestNG, SuiteRunner, or another alternative test runner in place of NetBeans' default JUnit. Examples are provided for SuiteRunner and TestNG. The resulting requirement is just one additional import in a standard project build file.
Project 'Sample' is a Java Standard Edition (JSE) project that is nicely organized for NetBeans, with a 'source' directory containing the source package hierarchy, and a 'test' directory containing the automated test suites for the same hierarchy.
Sample/ source/ org/ example/ Sample.java ... test/ org/ example/ SampleTests.java ...
In NetBeans it is simple to create a new standard project (File / New
Project / General / Java Project with Existing Sources), and follow the dialog
to add the 'source' directory to the Source Package Folders and the 'test'
directory to the Test Package Folders. The hierarchy appears in the Project
pane. The project pane also shows folders for Libraries and Test Libraries.
Since this project does not use JUnit for test cases, we can remove the
default JUnit test library from Test Libraries, and add the alternative
testing library (such as the jar for SuiteRunner or TestNG).
(For libraries are used in multiple projects, consider adding them to
the Library Manager [Tools / Library Manager] so you can manage the
path in one place.)
We can now compile the project (Build / Build Main Project). If it
has a class with a static void main(String[])
method,
then we can also run the project (Run / Run Main Project) and run it in the
debugger (Run / Debug Main Project). No problem with NetBeans
yet.
The problems begin when we try to work on the test suites. The first problem appears when we attempt to run the project tests (Projects pane, right click on the project name, Test Project in pop-up context menu). The tests successfully compile, but while attempting to run the tests, NetBeans produces errors like the following:
Testsuite: org.example.SampleTests Tests run: 1, Failures: 1, Errors: 0, Time elapsed: 2.754 sec Testcase: warning(junit.framework.TestSuite$1): FAILED No tests found in org.example.ExampleTest junit.framework.AssertionFailedError: No tests found in org.example.SampleTests
The same problem arises when we try to run an individual test
suite (Projects pane, right-click SampleTests.java
file, Run file in
pop-up menu).
Another problem arises when we try to run an individual test suite in the
debugger (Projects pane, open package hierarchy under Test Packages folder,
right-click SampleTests.java
file, Debug File in pop-up menu).
This time NetBeans complains it cannot find the JUnit test runner even though
the test suite does not use JUnit.
java.lang.NoClassDefFoundError: junit/textui/TestRunner
This is related to having removed the JUnit library. But putting it back doesn't help, it just produces the same error as above:
1) warning(junit.framework.TestSuite$1) junit.framework.AssertionFailedError: No tests found in org.example.SampleTests
The JUnit test runner expects test classes to derive from the JUnit package classes and interfaces, and follow the JUnit naming conventions, so it cannot run test suites written for other test runners.
How can we get NetBeans to use an alternative test runner instead? An answer will lie in NetBeans' hooks via the Ant build files.
Apache Ant is an extensible scripting tool for building Java projects. NetBeans uses Apache Ant build-files to provide hooks for defining what programs will be invoked on which files when a project is compiled, run, tested, debugged, etc. We can customize the build file to invoke a different test runner for each project.
When creating a NetBeans project for an existing system, NetBeans can either (a) generate a build file, resulting in a "standard project" (New Project / General / Java Project with Existing Sources), or (b) use a previously existing build file, resulting in a "free-form project" (New Project / General / Java Project with Existing Ant Script).
Since our Sample project did not have a pre-existing Ant
build.xml
file, we chose (a), and NetBeans generated a
NetBeans "standard project" build file for us in the project directory.
This build.xml
looks something like the following.
<?xml version="1.0" encoding="UTF-8"?> <!-- You may freely edit this file. See commented blocks below for --> <!-- some examples of how to customize the build. --> <project name="Sample" default="default" basedir="."> <description>Builds, tests, and runs the project Sample.</description> <import file="nbproject/build-impl.xml"/> <!-- There exist several targets which are by default empty and which can be used for execution of your tasks. ... For list of available properties check the imported nbproject/build-impl.xml file. Another way to customize the build is by overriding existing main targets. ... --> </project>
Its main purpose is to import
nbproject/build-impl.xml
, but the comments also point out
that the targets implemented by build-impl.xml are designed so they
can be overridden in the build.xml file.
(If your project's build.xml
file does not import
nbproject/build-impl.xml
,
then it is a "free-form project" with a custom Ant build-file, not a
"standard project". The examples below may be adapted for your
build-file, but don't expect to cut and paste them, because your project's
property names will be different. See
related articles for free-form projects.)
The build-impl.xml file contains targets we can override to fix the problems we encountered.
-do-test-run
is the core for Test Project.-do-test-run-single
is the core for Run File
(on a test file)-debug-start-debuggee-test
is the core for
Debug File (on a test file)The default definitions of the targets -do-test-run
and
-do-test-run-single
are similar, they invoke the JUnit ant-task
definition with properties for the class files to run
(test.includes
in the case of -do-test-run-single
),
the classpath (run.test.classpath
), where to write the output
report (build.test.results.dir
), etc. We can override this
definition to call a different test runner instead.
The default definition of -debug-start-debuggee-test
starts up a debugging instance running junit.textui.TestRunner on the
class name from property test.class
, using the classpath
in property debug.test.classpath
. We can also override
this definition to call a different test runner instead.
(As of NetBeans 6.5, the project "Compile on save" option must be
turned off for some target overrides to work [NetBeans issue #145772].)
In the next sections will show how this can be done for two example test runners, SuiteRunner and TestNG. TestNG has more options, so it is more complex; we start with the simpler Artima SuiteRunner.
The Debug Test File override is the most straightforward, so we begin
there. The -debug-start-debuggee-test
definition calls
<debug> which is defined by build-impl.xml as a macro which expands into
the full <java>
call with internal details. Since we're
just supplying a different suite running class, it is straightforward to
replace the suite-running class with org.suiterunner.Runner
, and
supply appropriate parameters to specify the test class. Runner also requires
a parameter to specify that we want a default text output reporter that
reports failures and aborts of tests, suites, or the run. (Parts specific to
SuiteRunner are in bold.)
<target name="-debug-start-debuggee-test" depends="init,compile-test,-suiterunner-debug-single" if="have.tests" /> <target name="-suiterunner-debug-single"> <fail unless="test.class">Must select one file in the IDE or set test.class</fail> <j3:debug classname="org.suiterunner.Runner" classpath="${debug.test.classpath}" xmlns:j3="http://www.netbeans.org/ns/j2se-project/3"> <customize> <arg line="-oFBAR"/> <!-- report failures and aborts to System.out --> <arg line="-s ${test.class}"/> </customize> </j3:debug> </target>
If we add this to a build.xml for a project using SuiteRunner for its test
suite, we can right click on a test file and invoke Debug File, and
SuiteRunner will be run using the NetBeans debugger, invoking all the
public test
... methods in the class. If a test fails, SuiteRunner
produces output similar the following:
TEST FAILED: SampleTests.testName(): Illegal char in name: Sample_Name org.suiterunner.TestFailedException: Illegal char in name: Sample_Name at org.suiterunner.Suite.verify(Suite.java:853) at org.example.SampleTests.testName(SampleTests.java:9) ...
NetBeans recognizes the stack trace and produces underlined links to the code.
Clicking the second line directs an editor pane to line 9 of
SampleTests.java
. Sometimes this is enough to correct a bad
test.
To use the debugger, set a breakpoint on the failing exception,
usually org.suiterunner.TestFailedException
. When the
test fails the debugger will stop, and you can explore the call stack,
look at the values of local variables, and otherwise use the debugger
to see what is happening. You may need to set a break pointer earlier
in the test method and rerun so that you can step through the methods
being tested.
Our first success! If you are in a hurry, this may be all you need to get debugging again. For a little more complete command coverage for SuiteRunner, read on.
Right-clicking on a test file and invoking Run File seems similar to Debug
file, but it turns out NetBeans has implemented them differently, making our
override of -do-test-run-single
a bit more difficult. While
Debug File supplied the classname, for Run File the selected file is supplied
as a .java
filename. In addition, the comments indicate someday
there may be multiple selected files rather than a single class, so we'd like
it to work with multiple files if possible.
Fortunately, the necessary conversion can be accomplished using Ant
<pathconvert>
. Each source filename is stripped of its
.java
suffix, each name is preceded by the -s
for a
test suite name, and the directory separators are translated to
'.
' package separators to produce a full classname. (Details are
explained in the filepath to classname
appendix.)
The Run Test File target can invoke org.suiterunner.Runner using the
standard <java>
Ant task, supplying the
classpath, using org.suiterunner.Runner as the class to run,
and providing the converted suite classnames parameter.
<target name="-do-test-run-single" depends="init,compile-test-single,-pre-test-run-single,-suiterunner-run-single" if="have.tests" /> <target name="-suiterunner-run-single"> <fail unless="test.includes">Must select some files in the IDE or set test.includes</fail> <!-- convert test.includes "pkg/ClassTests.java" to "-s pkg.ClassTests" --> <pathconvert property="suiterunner.classnames.args" pathsep=" " dirsep="."> <path><filelist dir="." files="${test.includes}"/></path> <mapper type="glob" from="*.java" to="*"/> <map from="${basedir}${file.separator}" to="-s "/> </pathconvert> <java classname="org.suiterunner.Runner" classpath="${run.test.classpath}" fork="true"> <arg line="-oFBAR"/> <!-- report failures and aborts to System.out --> <arg line="${suiterunner.classnames.args}"/> </java> </target>
If we add this to a build.xml for a project using SuiteRunner for its test suite, we can right click on a test file and invoke Run File, and the file will be run. If a test fails, SuiteRunner produces output similar the following (just like when run in the debugger):
TEST FAILED: SampleTests.testName(): Illegal char in name: Sample_Name org.suiterunner.TestFailedException: Illegal char in name: Sample_Name at org.suiterunner.Suite.verify(Suite.java:853) at org.example.SampleTests.testName(SampleTests.java:9) ...
NetBeans recognizes the stack trace and produces underlined links to the
code (just as it did when the file was run in the debugger). Clicking the
second line directs an editor pane to line 9 of SampleTests.java
.
Sometimes this is enough to correct a bad test.
For Test Project, there are two cases depending on whether there is a file
that describes which test cases should be run and their order. For
SuiteRunner, this file is the recipe.srj
file. If there is no
such file, then as a default the Ant target should simply run all test files
in the test directory.
(Test suite classes using SuiteRunner may have
subsuites which are other test suite classes, so a project's SuiteRunner
tests may be organized with a root "AllSuites" class rather than a recipe.
To run just the "AllSuites" class so suites aren't run more than once,
either run the AllSuites class via the Run File command, or create a
recipe.srj
file for just the AllSuites class.)
If there is a test/recipe.srj
file in the NetBeans
project, then SuiteRunner can be invoked directly on this file,
perhaps as follows:
<target name="-do-test-run" depends="init,compile-test,-post-compile,-pre-test-run,-suiterunner-run-recipe" if="have.tests" /> <target name="-suiterunner-run-recipe"> <echo level="info">Running ${test.src.dir}${file.separator}recipe.srj</echo> <java classname="org.suiterunner.Runner" classpath="${run.test.classpath}" fork="true"> <arg line="${test.src.dir}${file.separator}recipe.srj"/> </java> </target>
If there is no test/recipe.srj
file, then by default the Ant
target should run all test classes in the test directory. As a convention,
assume that all classes whose names end with ...Test
or
...Tests
are test classes to include. All test classes extend
org.suiterunner.Suite
, so also assume class names ending with
...Suite
are also test classes to include.
The target needs to collect all class names in the test directory that
match these patterns and add them to the command line. As in
-do-test-run-single
, each name will be preceded by
-s
on the command line.
(test.src.dir
may be a relative path or an absolute path.
One of the two <map>
clauses below should match
the fileset prefixes and replace them with "-s "
.)
<target name="-do-test-run" depends="init,compile-test,-post-compile,-pre-test-run,-suiterunner-run-classes" if="have.tests" /> <target name="-suiterunner-run-classes"> <!-- convert "pkg/ClassTests.java" to "-s pkg.ClassTests" --> <pathconvert property="suiterunner.classnames.args" pathsep=" " dirsep="."> <path> <fileset dir="${test.src.dir}" includes="**/*Test.java"/> <fileset dir="${test.src.dir}" includes="**/*Tests.java"/> <fileset dir="${test.src.dir}" includes="**/*Suite.java"/> </path> <mapper type="glob" from="*.java" to="*"/> <!-- map for relative test.src.dir --> <map from="${basedir}${file.separator}${test.src.dir}${file.separator}" to="-s "/> <!-- map for absolute test.src.dir --> <map from="${test.src.dir}${file.separator}" to="-s "/> </pathconvert> <echo level="info">Running -oFBAR ${suiterunner.classnames.args}</echo> <java classname="org.suiterunner.Runner" classpath="${run.test.classpath}" fork="true"> <arg line="-oFBAR"/> <!-- report failures and aborts to System.out --> <arg line="${suiterunner.classnames.args}"/> </java> </target>
For running NetBeans on one project with a SuiteRunner test suite, one of the
above may be enough to get going. But if some projects have a recipe file and
some do not, it would be simpler these two approaches could be combined, so
that the target will run the test/recipe.srj
file if it exists,
otherwise it will run classnames in the test directory. We'll
return to that issue after introducing targets
for TestNG.
A basic Debug Test File override is fairly straightforward for TestNG,
though there are a few more parameters with details to notice that are
specific for TestNG. As with the SuiteRunner definition, the definition for
TestNG below uses the NetBeans debug
macro. It invokes TestNG
with command line parameters. (Parts specific to TestNG are in bold.)
<target name="-debug-start-debuggee-test" depends="init,compile-test,-testng-debug-single" if="have.tests" /> <target name="-testng-debug-single"> <fail unless="test.class">Must select one file in the IDE or set test.class</fail> <j3:debug xmlns:j3="http://www.netbeans.org/ns/j2se-project/3" classname="org.testng.TestNG" classpath="${debug.test.classpath}"> <customize> <jvmarg line="-enableassertions"/> <!-- TestNG tests often use assert --> <jvmarg value="-Dtestng.test.classpath=${build.test.classes.dir}"/> <arg line="-d ${build.test.results.dir}"/> <arg line="-log 2 -usedefaultlisteners true"/> <!-- show stack traces --> <arg line="-sourcedir ${test.src.dir}"/> <!-- for jdk1.4 annotations --> <arg line="-suitename ${test.class}"/> <arg line="-testname ${test.class}"/> <arg line="-testclass ${test.class}"/> </customize> </j3:debug> </target>
-enableassertions
is necessary because one standard
way to write a TestNG test method is to use an assert
, which
throws an AssertionError
if the test fails. If assertions
are not enabled, the test condition of these test methods will not be
run.testng.test.classpath
is an efficiency option that tells
TestNG where to look for test classes, rather than the whole classpath.-d
tells TestNG reporters where to write report files.
For NetBeans projects, writing testing reports under the build/test
directory is convenient because there they are removed with the build
directory when the project is cleaned.-log 2 -usedefaultlisteners true
tells TestNG to produce the
output text that includes stack traces on errors. NetBeans displays
source locations in stack traces as clickable links.-suitename
and -testname
are used in the
output text and also the report.-testclass
is the class to test.After adding this to a build.xml for a project using TestNG for its
test suite, a right click on a test file, followed by a click on Debug
File, will use the NetBeans debugger to run TestNG on that file.
TestNG will run all the test methods annotated with
@Test
. If a test fails, TestNG can produce output
similar the following (where Bad Name: Sample
is the text
of the assertion message).
FAILED: testName
java.lang.AssertionError: Illegal char in name: Sample_Name
at org.example.SampleTests.testName(SampleTests.java:9)
... Removed 21 stack frames
NetBeans recognizes the stack trace and produces links to the code.
In this case, clicking the line directs an editor pane to line 9 of
SampleTests.java
. Sometimes this is enough to correct a bad
test.
To use the debugger, set a breakpoint on the failing exception,
usually java.lang.AssertionError
, and run Debug File
again. When the test fails the debugger will stop, and you can
explore the call stack, look at the values of local variables, and
otherwise use the debugger to see what is happening. You may need to
set a break pointer earlier in the test method and Debug File again so
that you can step through the methods being tested.
Success! If you are in a hurry, this may be all you need to get debugging your TestNG test suite again. If you need to use the TestNG groups feature to run a subset of the test methods in the debugger, see the debug groups appendix. For a little more complete command coverage for TestNG, read on.
As with SuiteRunner, the Run Test File must take its input as a
filename, or possibly multiple filenames, in
test.includes
. The code below converts the filenames to
classnames, each preceded by -testclass
.
As of TestNG5.4, while the documentation
claims that -testclass
accepts a comma separated list,
that has not been implemented, so each class must be preceded by a
-testclass
marker.
<pathconvert property="testng.classnames.args" dirsep="." pathsep=" "> <path> <filelist dir="." files="${test.includes}"/> </path> <mapper type="glob" from="*.java" to="*"/> <map from="${basedir}${file.separator}" to="-testclass "/> </pathconvert>
A similar transformation produces the suitename from the class name (or appending classnames if there are multiple selected classes in the future).
<pathconvert property="testng.suitename" pathsep="_" dirsep="."> <path> <filelist dir="." files="${test.includes}"/> </path> <mapper type="glob" from="*.java" to="*"/> <map from="${basedir}${file.separator}" to=""/> </pathconvert>
TestNG is invoked with nearly the same parameters as in the Debug Test File
case (see description above), but with
the transformed classname(s) for -testclass
, and for
-suitename
and -testname
.
<java classname="org.testng.TestNG" classpath="${run.test.classpath}" fork="true" resultproperty="testng.result.code"> <jvmarg line="-enableassertions"/> <!-- TestNG tests often assert --> <jvmarg value="-Dtestng.test.classpath=${build.test.classes.dir}"/> <arg line="-d ${build.test.results.dir}"/> <arg line="-log 2 -usedefaultlisteners true"/> <!-- show stack trace --> <arg line="-sourcedir ${test.src.dir}"/> <!-- for jdk1.4 annotations --> <arg line="-suitename ${testng.suitename}"/> <arg line="-testname ${testng.suitename}"/> <arg line="${testng.classnames.args}"/> </java>
Clearly, TestNG is invoked via the <java>
task rather
than <debug>
, so it will not be run with the debugger.
TestNG also provides a <testng>
Ant task. However, when
NetBeans runs TestNG using the <testng>
task, NetBeans does
not create links for source code locations in stack traces in the output, so
using the <java>
task is preferable.
NetBeans checks the tests.failed
property, and ends the run
with "BUILD FAILED" if it was set, otherwise with "BUILD SUCCESSFUL". TestNG
produces (via System.exit(int)
) the result code 0 if there were
no test failures, otherwise it produces result code 1. The
resultproperty
attribute above stores this result in the
testng.result.code
property. The following code sets
tests.failed
if the result code is not "0".
<condition property="tests.failed"> <not> <equals arg1="0" arg2="${testng.result.code}"/> </not> </condition>
All these parts can now be placed in a
-do-test-run-single
target in build.xml
,
overriding the original from nbproject/build-impl.xml
.
<target name="-do-test-run-single" depends="init,compile-test-single,-pre-test-run-single,-testng-run-single" if="have.tests"/> <target name="-testng-run-single"> <fail unless="test.includes">Must select some files in the IDE or set test.includes</fail> ... parts described above ... </target>
With this definition, right clicking on a test file and invoking Run File
will run TestNG on tests in the selected file. If a test fails, then it will
produce output something like the following, where the underlined stack trace
line is clickable and directs the editor to display it, in this case line 9 of
SampleTests.java
. That may be enough to diagnose a bad test, or
to set a breakpoint at the beginning of the test (to step through the problem
in the debugger in the next debug session).
FAILED: testName
java.lang.AssertionError: Illegal char in name: Sample_Name
at org.example.SampleTests.testName(SampleTests.java:9)
... Removed 21 stack frames
At the end of the output, NetBeans reports something like:
BUILD FAILED (total time: 2 seconds)If no tests failed, then NetBeans reports something like:
BUILD SUCCESSFUL (total time: 2 seconds)
For Test Project, there are again two cases depending on whether there is a
file that describes which test cases should be run. For TestNG, this file is
the testng.xml
file. If there is no such file, then as a default
the Ant target should run all methods annotated as a @Test
in all
files in the test directory.
If there is a test/testng.xml
file under the NetBeans project
directory, then TestNG can be invoked directly on this file, with a target
something like below. The parameters are mostly identical to the Debug Test File or Run Test File cases, but instead of
supplying -testclass
parameters, it provides the
testng.xml
filepath. The -suitename
and
-testname
parameters are omitted (those names are provided in the
testng.xml
file).
<target name="-do-test-run" depends="init,compile-test,-post-compile,-pre-test-run,-testng-test-run-xml" if="have.tests" /> <target name="-testng-test-run-xml"> <java classname="org.testng.TestNG" classpath="${run.test.classpath}" fork="true" resultproperty="testng.result.code"> <jvmarg line="-enableassertions"/> <!-- TestNG tests often assert --> <jvmarg value="-Dtestng.test.classpath=${build.test.classes.dir}"/> <arg line="-d ${build.test.results.dir}"/> <arg line="-log 2 -usedefaultlisteners true"/> <!-- show stack trace --> <arg line="-sourcedir ${test.src.dir}"/> <!-- for jdk1.4 annotations --> <arg line="${test.src.dir}${file.separator}testng.xml"/> <!-- after '-' options --> </java> <condition property="tests.failed"> <not> <equals arg1="0" arg2="${testng.result.code}"/> </not> </condition> </target>
If there is no test/testng.xml
file, then the project needs a
target that invokes all the methods annotated with the annotation
@Test
. TestNG can examine the class files to find those that
have @Test
annotations, so there is no need to rely on class
naming conventions as was done for SuiteRunner, so the
pathconvert
is simpler.
<target name="-do-test-run" depends="init,compile-test,-post-compile,-pre-test-run,-testng-test-run-classes" if="have.tests" /> <target name="-testng-test-run-classes"> <pathconvert property="testng.classnames.args" pathsep=" "> <path> <fileset dir="${build.test.classes.dir}" includes="**/*.class"/> </path> <map from="${basedir}${file.separator}" to="-testclass "/> </pathconvert> <java classname="org.testng.TestNG" classpath="${run.test.classpath}" fork="true" resultproperty="testng.result.code"> <jvmarg line="-enableassertions"/> <!-- TestNG tests often assert --> <jvmarg value="-Dtestng.test.classpath=${build.test.classes.dir}"/> <arg line="-d ${build.test.results.dir}"/> <arg line="-log 2 -usedefaultlisteners true"/> <!-- show stack trace --> <arg line="-sourcedir ${test.src.dir}"/> <!-- for jdk1.4 annotations --> <arg value="-suitename"/> <arg value="All test classes"/> <arg value="-testname"/> <arg value="All test classes"/> <arg line="${testng.testclasses}"/> </java> <condition property="tests.failed"> <not> <equals arg1="0" arg2="${testng.result.code}"/> </not> </condition> </target>
Invoking TestNG is similar to the Run Test File case, except it uses "All test classes" as the suite and test name.
One of these two targets will work for a project. However, it is more
convenient if a target could choose between the two implementations depending on
whether a testng.xml
file exists in a project. The next section
looks at two ways to implement exclusive alternative targets.
<antcall>
for alternative targetsIn Ant there are two ways for target A to make sure target B has a chance
to run. The standard way is for A to list B among its dependencies. The
alternative way is for A to call B via <antcall>
. Below
each of the two techniques is illustrated implementing the alternative targets
for Test Project, followed by discussion of some tradeoffs in NetBeans. The
examples below use the SuiteRunner targets. (For TestNG the
target
attributes follow the same format but use
testng
in the names and check for the
test/testng.xml
file.)
Above are two approaches for implementing a Test Project target. One runs tests listed in a file, the other gathers all test classes. These can be invoked by from single Ant super-target by setting a property if the file exists, so the targets that implement each approach can be conditioned on whether or not the property is set.
To set property suiterunner.recipe
indicating
whether the file test/recipe.srj
exists, create a target using the
<available>
task:
<target name="-check-suiterunner-recipe"> <available property="suiterunner.recipe" file="${test.src.dir}${file.separator}recipe.srj" value="${test.src.dir}${file.separator}recipe.srj"/> </target>
The two conditional targets for the two implementations will use the result
of this availability test. Both depend on the
-check-suiterunner-recipe
target, to make sure they only run
after it. The condition in their if
or unless
property is checked after the depends
targets have had a
chance to run.
One will run if file recipe.srj
exists...
<target name="-suiterunner-run-recipe" depends="-check-suiterunner-recipe" if="suiterunner.recipe"> ... </target>
... and the other will run unless the
recipe.srj
file exists (in other words, it runs only if
file recipe.srj
is nonexistent).
<target name="-suiterunner-run-classes" depends="-check-suiterunner-recipe" unless="suiterunner.recipe"> ... </target>
The -do-test-run
target makes sure both of these get the
chance to run when it does by adding them to its depends list (indirectly via
-suiterunner-run
for modularity).
<target name="-do-test-run" depends="init,compile-test,-post-compile,-pre-test-run,-suiterunner-run" if="have.tests" /> <target name="-suiterunner-run" depends="-suiterunner-run-recipe,-suiterunner-run-classes"/>
This is the standard way to encode the dependencies between targets
in Ant. Next is an alternative way using <antcall>
.
<antcall>
invocationThe two approaches for implementing a Test Project target described earlier
could also be implemented using <antcall>
. Rather than using
depends
to control the ordering requirements, the main target can
call the alternative targets directly using <antcall>
.
<target name="-suiterunner-run"> <available property="suiterunner.recipe" file="${test.src.dir}${file.separator}recipe.srj" value="${test.src.dir}${file.separator}recipe.srj" /> <!-- just one of these runs, depending on existence of test/recipe.srj --> <antcall target="-suiterunner-test-run-recipe"/> <antcall target="-suiterunner-test-run-classes"/> </target>
The two conditional targets for the two implementations can depend on
the result of this availability test. One will run if the
recipe.srj
file does exist...
<target name="-suiterunner-run-recipe" if="suiterunner.recipe"> ... </target>
... and the other will run unless the
recipe.srj
file exists (in other words, it runs only if
the recipe.srj
file does not exist).
<target name="-suiterunner-test-run-classes" unless="suiterunner.recipe"> ... </target>
Unlike the 'depends' case, in this case there are no depends
attributes; the order is hard-coded in the target above. Also, notice the
<available>
check must be written inline in the
-suiterunner-test-run
target.
The <antcall>
version seems shorter, and for programmers
used to writing sequential procedures, more straightforward. However,
<antcall>
is not simply a subroutine call. In addition to
being invoked, the target of an <antcall>
also receives a
private set of all property bindings, so it can modify them as needed
for the sub-target without interfering in the caller's state. This means
that modifications made by the sub-target are not visible to the caller.
That is why in the <antcall>
version, the
<available>
check is made inline, not in a sub-target that
was target of an <antcall>
.
For NetBeans, the target running the tests can set the property
tests.failed
to indicate to NetBeans whether any tests failed.
If some tests failed, then NetBeans will report "BUILD FAILED", and if no
tests failed, NetBeans will report "BUILD SUCCESSFUL". However, if this
target is invoked via an <antcall>
, then any changes will
be discarded when it returns, so the property NetBeans sees will not be set
and NetBeans will always report "BUILD SUCCESSFUL" even if some test
failed.
(Unfortunately, currently that is not the end of the story. The NetBeans
CodeCoverage module uses the Emma code coverage package to help find code
blocks that have been missed by a test suite. It affects a test run as
follows: After compilation it instruments the non-test class files, so that
when the tests are running they record which blocks were called by the tests.
After the test run completes, it scans the recorded data and colors the
non-test source code to indicate which lines ran sometime during the tests.
The problem is that with NetBeans 5.5, codecoverage 2.2.1, and the depends
version of the Test Project target, the instrumentation doesn't occur until
after the tests have run. I have not found the reason for this difference,
but running with the <antcall>
version works around the
problem. Maybe creating a copy of all the properties somehow triggers the
instrumentation target.)
build.xml
Finally, using the techniques described above, all the overriding
definitions can be combined into one file that can be imported from the
project build.xml
file of a NetBeans standard Java project,
overriding the definitions in build-impl.xml
. This is simpler
than copying and pasting the definitions. For example, for a project that
uses TestNG for its test Suite, download the TestNG file and add the import
element to import that file:
<project name="MyProject" default="default" basedir="."> <description>Builds, tests, and runs the project MyProject.</description> <import file="nbtargets-testng.xml" /> <import file="nbproject/build-impl.xml" /> </project>
Notice that the overriding targets are imported before the
build-impl.xml
(among imports, first target definition
prevails).
(As of NetBeans 6.5, the project "Compile on save" option must be
turned off for some target overrides to work [NetBeans issue #145772].)
With this one additional import, NetBeans now calls the desired test runner for the test file "Debug File" command, the test file "Run File" command, and the "Test Project" commands.
Below are links to the final versions of the files for SuiteRunner and TestNG. I hope that there has been enough information here for others to build similar files for other Java test runners.
<antcall>
instead of
depends
for alternative targets.<antcall>
instead of depends
for alternative
targets. It reports "BUILD SUCCESSFUL" even when there are test
failures.build.xml
by overriding the
<junit>
to call JUnit4.Right-clicking on a test file and invoking Run File seems similar to
Debug file, but it turns out NetBeans has implemented them
differently, making our override of -do-test-run-single
a
bit more difficult. While Debug File supplied the classname, for Run
File the selected file is supplied as a Java filename.
We can examine the value by adding an initial overriding definition to
our build file with a diagnostic <echo>
.
<target name="-do-test-run-single" ...> <fail unless="test.includes">Must select some files in the IDE or set test.includes</fail> <echo>test.includes is "${test.includes}"</echo> <target>
Now Run File on a test file, and you should see the value of
test.includes
in the output. For the SampleTests.java
file, it should be something like
test.includes is "org/example/SampleTests.java"
We need to turn this into "org.example.SampleTests".
Fortunately, Ant supplies the <pathconvert>
task
which can perform name conversions, directory separator conversions,
path separator conversion, and directory mapping. First of all, we
need to get rid of the file suffix, so we might try the following:
<target name="-do-test-run-single" ...> <fail unless="test.includes">Must select some files in the IDE or set test.includes</fail> <echo>test.includes is "${test.includes}"</echo> <pathconvert property="test.includes.classnames"> <path><filelist dir="." files="${test.includes}"/></path> <mapper type="glob" from="*.java" to="*"/> </pathconvert> <echo>test.includes.classnames is "${test.includes.classnames}"</echo> </target>
The result may be something like this:
test.includes.classnames is "c:\projects\Sample\source\org\example\SampleTests"
It trimmed off the file suffix, but it also converted the relative path
into a full path, and converted to native directory separators. We
can get rid of the path prefix using <map>
:
<target name="-do-test-run-single" ...> <fail unless="test.includes">Must select some files in the IDE or set test.includes</fail> <echo>test.includes is "${test.includes}"</echo> <pathconvert property="test.includes.classnames"> <path><filelist dir="." files="${test.includes}"/></path> <mapper type="glob" from="*.java" to="*"/> <map from="${basedir}${file.separator}" to=""/> </pathconvert> <echo>test.includes.classnames is "${test.includes.classnames}"</echo> </target>
This says to trim off the project base directory from the beginning of the path name. Now our result is closer.
test.includes.classnames is "org\example\SampleTests"
We still need dotted classnames, so we tell
<pathconvert>
to use dots as the directory
separator with dirsep="."
(see version below).
This gives us the dotted classname we are looking for.
test.includes.classnames is "org.example.SampleTests"
The first lines of the default implementation provide a clue to an additional difference. The property holding the filename may someday hold more than one filename, so we want to be able to handle that case as well.
<target name="-do-test-run-single" ...> <fail unless="test.includes">Must select some files in the IDE or set test.includes</fail> ...
The classnames will be used on a command line, and SuiteRunner
requires that each one be preceded by -s
, all separated
by spaces. To get this effect, we can make the following additions:
<map>
, add -s
in place of each removed path prefix.<pathconvert>
, add a pathsep=" "
so each "-s name" is separated by a space.<target name="-do-test-run-single" ...> <fail unless="test.includes">Must select some files in the IDE or set test.includes</fail> <echo>test.includes is "${test.includes}"</echo> <pathconvert property="test.includes.classnames" pathsep=" " dirsep="."> <path><filelist dir="." files="${test.includes}"/></path> <mapper type="glob" from="*.java" to="*"/> <map from="${basedir}${file.separator}" to="-s "/> </pathconvert> <echo>test.includes.classnames is "${test.includes.classnames}"</echo> </target>
Now our result is as follows, ready for the SuiteRunner command line.
test.includes.classnames is "-s org.example.SampleTests"
Sometimes a test class may contain multiple tests, and for
debugging you would like to focus on certain tests and skip the rest,
perhaps because they invoke breakpointed code. While it is still
possible to use brute-force techniques like commenting out the other
tests, or creating a new first test method that calls the test method
you want, these techniques pollute the test source code in ways that
you need to remember to clean up later. TestNG offers another
technique: you can tag the methods of interest with an annotation such
as @Test(groups={"debug123"})
, and then tell TestNG to
only run methods in group debug123
. Normally TestNG
ignores the tag, so forgetting to clean up the tag doesn't change test
suite coverage or test counts.
The property testng.debug.groups
can be set to a tag
such as debug123
or a comma separated list of tags such
as debug123,debug124
by a NetBeans user via Tools /
Options / Misc / Ant / Properties (see also
alternative).
testng.debug.groups=debug123,debug124
For the Ant target, extending the basic target for Debug File (see
TestNG: Debug File) by adding the
-groups
command line parameter is straightforward.
One problem with this target is that it requires
testng.debug.groups
to be set, and it won't run any tests
not tagged with a group. A solution to this problem is to create two
targets, one that runs all tests when testng.debug.groups
has not been set or is empty, and one that just runs the tagged groups
when it is set.
The following target sets testng.debug.groups.nonempty
if testng.debug.groups
has been set and it is not the
empty string.
<target name="-check-testng-debug-groups"> <condition property="testng.debug.groups.nonempty"> <and> <isset property="testng.debug.groups"/> <not><equals arg1="${testng.debug.groups}" arg2="" trim="true"/></not> </and> </condition> </target>
For the empty (no groups) case, this target can run unless
testng.debug.groups.nonempty
has been set. The body is
identical to the basic version.
<target name="-testng-debug-single-nogroups" depends="-check-testng-debug-groups" unless="testng.debug.groups.nonempty"> ... </target>
For the tagged groups case, this target only can run if
testng.debug.groups.nonempty
has been set. The body constructs a
string suitable for the suite and test name. Because the suite and test names
are used for directory names in the report, the commas must be removed. Here
each comma is replaced with a lowbar '_
' using
<pathconvert>
(it doesn't matter that group names are not
paths).
<target name="-testng-debug-single-groups" depends="-check-testng-debug-groups" if="testng.debug.groups.nonempty"> <fail unless="test.class">Must select one file in the IDE or set test.class</fail> <!-- For suite name, convert comma separated list of groups (not files) to '_' separated --> <pathconvert property="testng.debug.groups.name" pathsep="_"> <path> <filelist dir="." files="${testng.debug.groups}"/> </path> <map from="${basedir}${file.separator}" to=""/> </pathconvert> <j3:debug xmlns:j3="http://www.netbeans.org/ns/j2se-project/3" classname="org.testng.TestNG" classpath="${debug.test.classpath}"> <customize> <jvmarg line="-enableassertions"/> <!-- TestNG tests often assert --> <jvmarg value="-Dtestng.test.classpath=${build.test.classes.dir}"/> <arg line="-d ${build.test.results.dir}"/> <arg line="-log 2 -usedefaultlisteners true"/> >!-- show stack trace --> <arg line="-sourcedir ${test.src.dir}"/> <!-- for jdk1.4 annotations --> <arg line="-suitename ${test.class}__${testng.debug.groups.name}"/> <arg line="-testname ${test.class}__${testng.debug.groups.name}"/> <arg line="-testclass ${test.class}"/> <arg line="-groups ${testng.debug.groups}"/> </customize> </j3:debug> </target>
Finally, a single target that depends on both versions. Only one
will be run, depending on whether testng.debug.groups
is
nonempty.
<target name="-testng-debug-single" depends="-testng-debug-single-groups,-testng-debug-single-nogroups" if="have.tests"/>
(Advanced Alternative: A potential drawback of using Tools / Options / Misc / Ant / Properties is that it is saved in the developer's NetBeans profile, so it is shared between all projects of that developer. An alternative is to keep temporary debug properties separate for each project as follows:
<project>
in build.xml
, add:
<property file="user.properties"/> <!-- optional file, for debug settings -->
user.properties
with
# Do not add this file to archived source code repository. # Optional temporary settings for debugging. testng.debug.groups=debug123,debug124
user.properties
files
(for example, see [CVS Manual: "Ignoring files via cvsignore"],
[SVN Manual: "Ignoring Unversioned Items"],
[Mercurial (Hg): ".hgignore (5)"], etc.).With this approach, a multitasking developer can have a separate
user.properties
file in each project directory, rather
than one set in Tools / Options / Misc / Ant / Properties for all
projects. Each developer working on the project can still set their
own debug properties in their local user.properties
file which is not shared in the source code repository. Ant treats
property files as optional, so for developers who do not create a
user.properties
file, it will be ignored.)