Ok, here is a quick layout of how JUnit 4 runs parameterized tests, but runs in JUnit 3.8.2.
I basically subclass and badly grab the TestSuite class to populate the list of tests according to the cross product testMethods and parameters.
Unfortunately, I had to copy several helper methods from TestSuite itself, and some details are not ideal, for example, the test names in the IDE are the same for parameter sets (JUnit 4.x appends [0] , [1] , ...).
However, this seems to work well in the text and in the AWT TestRunner that come with JUnit as well as in Eclipse.
Here is the ParameterizedTestSuite parameter and further down the (silly) example of a parameterized test using it.
(final note: I wrote this using Java 5 in mind, it should be trivial to adapt to 1.4 if necessary)
ParameterizedTestSuite.java:
package junit.parameterized; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; public class ParameterizedTestSuite extends TestSuite { public ParameterizedTestSuite( final Class<? extends TestCase> testCaseClass, final Collection<Object[]> parameters) { setName(testCaseClass.getName()); final Constructor<?>[] constructors = testCaseClass.getConstructors(); if (constructors.length != 1) { addTest(warning(testCaseClass.getName() + " must have a single public constructor.")); return; } final Collection<String> names = getTestMethods(testCaseClass); final Constructor<?> constructor = constructors[0]; final Collection<TestCase> testCaseInstances = new ArrayList<TestCase>(); try { for (final Object[] objects : parameters) { for (final String name : names) { TestCase testCase = (TestCase) constructor.newInstance(objects); testCase.setName(name); testCaseInstances.add(testCase); } } } catch (IllegalArgumentException e) { addConstructionException(e); return; } catch (InstantiationException e) { addConstructionException(e); return; } catch (IllegalAccessException e) { addConstructionException(e); return; } catch (InvocationTargetException e) { addConstructionException(e); return; } for (final TestCase testCase : testCaseInstances) { addTest(testCase); } } private Collection<String> getTestMethods( final Class<? extends TestCase> testCaseClass) { Class<?> superClass= testCaseClass; final Collection<String> names= new ArrayList<String>(); while (Test.class.isAssignableFrom(superClass)) { Method[] methods= superClass.getDeclaredMethods(); for (int i= 0; i < methods.length; i++) { addTestMethod(methods[i], names, testCaseClass); } superClass = superClass.getSuperclass(); } return names; } private void addTestMethod(Method m, Collection<String> names, Class<?> theClass) { String name= m.getName(); if (names.contains(name)) return; if (! isPublicTestMethod(m)) { if (isTestMethod(m)) addTest(warning("Test method isn't public: "+m.getName())); return; } names.add(name); } private boolean isPublicTestMethod(Method m) { return isTestMethod(m) && Modifier.isPublic(m.getModifiers()); } private boolean isTestMethod(Method m) { String name= m.getName(); Class<?>[] parameters= m.getParameterTypes(); Class<?> returnType= m.getReturnType(); return parameters.length == 0 && name.startsWith("test") && returnType.equals(Void.TYPE); } private void addConstructionException(Exception e) { addTest(warning("Instantiation of a testCase failed " + e.getClass().getName() + " " + e.getMessage())); } }
ParameterizedTest.java:
package junit.parameterized; import java.util.Arrays; import java.util.Collection; import junit.framework.Test; import junit.framework.TestCase; import junit.parameterized.ParameterizedTestSuite; public class ParameterizedTest extends TestCase { private final int value; private int evilState; public static Collection<Object[]> parameters() { return Arrays.asList( new Object[] { 1 }, new Object[] { 2 }, new Object[] { -2 } ); } public ParameterizedTest(final int value) { this.value = value; } public void testMathPow() { final int square = value * value; final int powSquare = (int) Math.pow(value, 2) + evilState; assertEquals(square, powSquare); evilState++; } public void testIntDiv() { final int div = value / value; assertEquals(1, div); } public static Test suite() { return new ParameterizedTestSuite(ParameterizedTest.class, parameters()); } }
Note: the evilState variable is here to show that all test instances are different, as it should be, and that there is no common state between them.