NetBeans and Alternative Test Runners:
Customizing Standard Java Projects

Contents

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.

Problems running tests in NetBeans

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.

Ant build-files in NetBeans

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.

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.

Overrides for SuiteRunner

Debug Test File

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.

Run Test File

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.

Test Project

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.

Overrides for TestNG

Debug Test File

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>

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.

Run Test File

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)

Test Project

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.

Depends Vs. <antcall> for alternative targets

In 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.)

Test Project: Depends invocation

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>.

Test Project: <antcall> invocation

The 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.

Trade-offs

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.)

Conclusion: Importing into 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.

Appendix: Files, Links, & Related Work

Files

nbtargets-suiterunner.xml
A file that can be imported before build-impl.xml to provide the command targets described in this article for SuiteRunner test suites.
nbtargets-suiterunner-cvg.xml
A variant of the above file that may work better with the NetBeans CodeCoverage module (as of NetBeans 5.5, CodeCoverage 2.2.1). It uses <antcall> instead of depends for alternative targets.
nbtargets-testng.xml
A file that can be imported before build-impl.xml to provide the command targets described in this article for TestNG test suites.
nbtargets-testng-cvg.xml
A variant of the above file that may work better with the NetBeans CodeCoverage module (as of NetBeans 5.5, CodeCoverage 2.2.1). It uses <antcall> instead of depends for alternative targets. It reports "BUILD SUCCESSFUL" even when there are test failures.
NetBeans and Alternative Test Runners
Latest version.
NetBeans
Extensible Java integrated development environment.
Ant
Extensible Java package building tool, replaces 'make' or scripts. Implementation is included in NetBeans.
TestNG
A "next generation" test runner making use of the Java Annotations introduced in Java 5 (jdk1.5). Annotations mark which methods of a test suite are tests, and can optionally be used to tag tests with one more group names, and declare dependencies between tests. It also provides means to write data driven test cases.
SuiteRunner 1.0beta6 (not beta7)
An alternative once popular a few years ago from the Artima article "Why we refactored JUnit". SuiteRunner created a simpler library with fewer classes, added setup/teardown, made it easier to integrate testing into other processes by factoring the reporter interface, and made it easier to write test suites with custom loops where each test case is represented by data rather than by a separate method. (Version 1.0beta6 was used for the examples. it has a minor bug where failed tests are added to test count twice. Version 1.0beta7 has bugs affecting the config parser.)
CodeCoverage 2.2.1
Optional experimental NetBeans module using the Emma coverage tool to identify code blocks that are never executed by a test suite run. (As of version 2.2.1, the codecoverage module produces spurious data-not-found message tabs when activated, and fails to reset its data before each run, so users must deactivate coverage and delete its file coverage/coverage.emma by hand).
TestNG Support 4.6
Implements partial NetBeans support for TestNG test classes.
  • Provides a TestNG test file creator (in New File).
  • Provides a Show Test Results command
  • Does not change behavior of Test Project (uses separate command)
  • Does not change behavior of Run File or Debug File on test files
"Importing a 3rd Party Project as a NetBeans Free-Form Project: Special NetBeans Actions"
Describes customizing Run File and Debug File for free-form projects, not standard projects, and only for source files, not test files.
Advanced Free-Form Project Configuration: Writing a Target to Run/Debug/Test a Single File
Describes customizing Run File and Debug File for free-form projects, not standard projects.
FAQ: use JUnit4 to run tests
Describes customizing build.xml by overriding the <junit> to call JUnit4.

Appendix: Ant Filepath to Classname

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".

Converting a filename to a classname

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"

Converting multiple filenames to multiple classnames

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:

<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"

Appendix: Debug TestNG Groups

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:

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.)