guerda's solution is good. Here's what I ended up doing (it's a mix of Luke Francl's recipe, which I linked before, and some other stuff I saw on the net):
import org.junit.runner.manipulation.Filter;
import org.junit.runner.Description;
public final class AntCLFilter extends Filter {
private static final String TEST_CASES = "tests";
private static final String ANT_PROPERTY = "${tests}";
private static final String DELIMITER = "\\,";
private String[] testCaseNames;
public AntCLFilter() {
super();
if (hasTestCases()) testCaseNames = getTestCaseNames();
}
public String describe() {
return "Filters out all tests not explicitly named in a comma-delimited list in the system property 'tests'.";
}
public boolean shouldRun(Description d) {
String displayName = d.getDisplayName();
// cut off the method name:
String testName = displayName.substring(0, displayName.indexOf('('));
if (testCaseNames == null) return true;
for (int i = 0; i < testCaseNames.length; i++)
if (testName.equals(testCaseNames[i]))
return true;
return false;
}
/**
* Check to see if the test cases property is set. Ignores Ant's
* default setting for the property (or null to be on the safe side).
**/
public static boolean hasTestCases() {
return
System.getProperty( TEST_CASES ) == null ||
System.getProperty( TEST_CASES ).equals( ANT_PROPERTY ) ?
false :
true;
}
/**
* Create a List of String names of test cases specified in the
* JVM property in comma-separated format.
*
* @return a List of String test case names
*
* @throws NullPointerException if the TEST_CASES property
* isn't set
**/
private static String[] getTestCaseNames() {
if ( System.getProperty( TEST_CASES ) == null ) {
throw new NullPointerException( "Test case property is not set" );
}
String testCases = System.getProperty( TEST_CASES );
String[] cases = testCases.split(DELIMITER);
return cases;
}
}
import org.junit.internal.runners.*;
import org.junit.runner.manipulation.Filter;
import org.junit.runner.manipulation.NoTestsRemainException;
public class FilteredRunner extends TestClassRunner {
public FilteredRunner(Class<?> clazz) throws InitializationError {
super(clazz);
Filter f = new AntCLFilter();
try {
f.apply(this);
} catch (NoTestsRemainException ex) {
throw new RuntimeException(ex);
}
}
}
Then I annotated my test class with:
@RunWith(FilteredRunner.class)
public class MyTest {
and put the following in my ant buildfile:
<target name="runtest" description="Runs the test you specify on the command
line with -Dtest=" depends="compile, ensure-test-name">
<junit printsummary="withOutAndErr" fork="yes">
<sysproperty key="tests" value="${tests}"/>
<classpath refid="classpath" />
<formatter type="plain" usefile="false"/>
<batchtest>
<fileset dir="${src}">
<include name="**/${test}.java"/>
</fileset>
</batchtest>
</junit>
the key line there being the sysproperty tag.
And now I can run
ant runtest -Dtest=MyTest -Dtests=testFoo,testBar
as desired. This works with JUnit 4.1 --- in 4.4, subclass from JUnit4ClassRunner, and in 4.5 and later, subclass from BlockJUnit4ClassRunner.