package org.junit.runners; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import org.junit.runner.Runner; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; import org.junit.runners.model.TestClass; import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParametersFactory; import org.junit.runners.parameterized.ParametersRunnerFactory; import org.junit.runners.parameterized.TestWithParameters; /** * The custom runner <code>Parameterized</code> implements parameterized tests. * When running a parameterized test class, instances are created for the * cross-product of the test methods and the test data elements. * <p> * For example, to test a Fibonacci function, write: * <pre> * @RunWith(Parameterized.class) * public class FibonacciTest { * @Parameters(name= "{index}: fib[{0}]={1}") * public static Iterable<Object[]> data() { * return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 }, * { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } }); * } * * private int fInput; * * private int fExpected; * * public FibonacciTest(int input, int expected) { * fInput= input; * fExpected= expected; * } * * @Test * public void test() { * assertEquals(fExpected, Fibonacci.compute(fInput)); * } * } * </pre> * <p> * Each instance of <code>FibonacciTest</code> will be constructed using the * two-argument constructor and the data values in the * <code>@Parameters</code> method. * <p> * In order that you can easily identify the individual tests, you may provide a * name for the <code>@Parameters</code> annotation. This name is allowed * to contain placeholders, which are replaced at runtime. The placeholders are * <dl> * <dt>{index}</dt> * <dd>the current parameter index</dd> * <dt>{0}</dt> * <dd>the first parameter value</dd> * <dt>{1}</dt> * <dd>the second parameter value</dd> * <dt>...</dt> * <dd>...</dd> * </dl> * <p> * In the example given above, the <code>Parameterized</code> runner creates * names like <code>[1: fib(3)=2]</code>. If you don't use the name parameter, * then the current parameter index is used as name. * <p> * You can also write: * <pre> * @RunWith(Parameterized.class) * public class FibonacciTest { * @Parameters * public static Iterable<Object[]> data() { * return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 }, * { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } }); * } * * @Parameter(0) * public int fInput; * * @Parameter(1) * public int fExpected; * * @Test * public void test() { * assertEquals(fExpected, Fibonacci.compute(fInput)); * } * } * </pre> * <p> * Each instance of <code>FibonacciTest</code> will be constructed with the default constructor * and fields annotated by <code>@Parameter</code> will be initialized * with the data values in the <code>@Parameters</code> method. * * <p> * The parameters can be provided as an array, too: * * <pre> * @Parameters * public static Object[][] data() { * return new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, * { 5, 5 }, { 6, 8 } }; * } * </pre> * * <h3>Tests with single parameter</h3> * <p> * If your test needs a single parameter only, you don't have to wrap it with an * array. Instead you can provide an <code>Iterable</code> or an array of * objects. * <pre> * @Parameters * public static Iterable<? extends Object> data() { * return Arrays.asList("first test", "second test"); * } * </pre> * <p> * or * <pre> * @Parameters * public static Object[] data() { * return new Object[] { "first test", "second test" }; * } * </pre> * * <h3>Create different runners</h3> * <p> * By default the {@code Parameterized} runner creates a slightly modified * {@link BlockJUnit4ClassRunner} for each set of parameters. You can locate an * own {@code Parameterized} runner that creates another runner for each set of * parameters. Therefore you have to locate a {@link ParametersRunnerFactory} * that creates a runner for each {@link TestWithParameters}. ( * {@code TestWithParameters} are bundling the parameters and the test name.) * The factory must have a public zero-arg constructor. * * <pre> * public class YourRunnerFactory implements ParameterizedRunnerFactory { * public Runner createRunnerForTestWithParameters(TestWithParameters test) * throws InitializationError { * return YourRunner(test); * } * } * </pre> * <p> * Use the {@link UseParametersRunnerFactory} to tell the {@code Parameterized} * runner that it should use your factory. * * <pre> * @RunWith(Parameterized.class) * @UseParametersRunnerFactory(YourRunnerFactory.class) * public class YourTest { * ... * } * </pre> * * @since 4.0 */ public class Parameterized extends Suite { /** * Annotation for a method which provides parameters to be injected into the * test class constructor by <code>Parameterized</code>. The method has to * be public and static. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public static @interface Parameters { /** * Optional pattern to derive the test's name from the parameters. Use * numbers in braces to refer to the parameters or the additional data * as follows: * <pre> * {index} - the current parameter index * {0} - the first parameter value * {1} - the second parameter value * etc... * </pre> * <p> * Default value is "{index}" for compatibility with previous JUnit * versions. * * @return {@link MessageFormat} pattern string, except the index * placeholder. * @see MessageFormat */ String name() default "{index}"; } /** * Annotation for fields of the test class which will be initialized by the * method annotated by <code>Parameters</code>. * By using directly this annotation, the test class constructor isn't needed. * Index range must start at 0. * Default value is 0. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public static @interface Parameter { /** * Method that returns the index of the parameter in the array * returned by the method annotated by <code>Parameters</code>. * Index range must start at 0. * Default value is 0. * * @return the index of the parameter. */ int value() default 0; } /** * Add this annotation to your test class if you want to generate a special * runner. You have to specify a {@link ParametersRunnerFactory} class that * creates such runners. The factory must have a public zero-arg * constructor. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface UseParametersRunnerFactory { /** * @return a {@link ParametersRunnerFactory} class (must have a default * constructor) */ Class<? extends ParametersRunnerFactory> value() default BlockJUnit4ClassRunnerWithParametersFactory.class; } private static final ParametersRunnerFactory DEFAULT_FACTORY = new BlockJUnit4ClassRunnerWithParametersFactory(); private static final List<Runner> NO_RUNNERS = Collections.<Runner>emptyList(); private final List<Runner> fRunners; /** * Only called reflectively. Do not use programmatically. */ public Parameterized(Class<?> klass) throws Throwable { super(klass, NO_RUNNERS); ParametersRunnerFactory runnerFactory = getParametersRunnerFactory( klass); Parameters parameters = getParametersMethod().getAnnotation( Parameters.class); fRunners = Collections.unmodifiableList(createRunnersForParameters( allParameters(), parameters.name(), runnerFactory)); } private ParametersRunnerFactory getParametersRunnerFactory(Class<?> klass) throws InstantiationException, IllegalAccessException { UseParametersRunnerFactory annotation = klass .getAnnotation(UseParametersRunnerFactory.class); if (annotation == null) { return DEFAULT_FACTORY; } else { Class<? extends ParametersRunnerFactory> factoryClass = annotation .value(); return factoryClass.newInstance(); } } @Override protected List<Runner> getChildren() { return fRunners; } private TestWithParameters createTestWithNotNormalizedParameters( String pattern, int index, Object parametersOrSingleParameter) { Object[] parameters= (parametersOrSingleParameter instanceof Object[]) ? (Object[]) parametersOrSingleParameter : new Object[] { parametersOrSingleParameter }; return createTestWithParameters(getTestClass(), pattern, index, parameters); } @SuppressWarnings("unchecked") private Iterable<Object> allParameters() throws Throwable { Object parameters = getParametersMethod().invokeExplosively(null); if (parameters instanceof Iterable) { return (Iterable<Object>) parameters; } else if (parameters instanceof Object[]) { return Arrays.asList((Object[]) parameters); } else { throw parametersMethodReturnedWrongType(); } } private FrameworkMethod getParametersMethod() throws Exception { List<FrameworkMethod> methods = getTestClass().getAnnotatedMethods( Parameters.class); for (FrameworkMethod each : methods) { if (each.isStatic() && each.isPublic()) { return each; } } throw new Exception("No public static parameters method on class " + getTestClass().getName()); } private List<Runner> createRunnersForParameters( Iterable<Object> allParameters, String namePattern, ParametersRunnerFactory runnerFactory) throws InitializationError, Exception { try { List<TestWithParameters> tests = createTestsForParameters( allParameters, namePattern); List<Runner> runners = new ArrayList<Runner>(); for (TestWithParameters test : tests) { runners.add(runnerFactory .createRunnerForTestWithParameters(test)); } return runners; } catch (ClassCastException e) { throw parametersMethodReturnedWrongType(); } } private List<TestWithParameters> createTestsForParameters( Iterable<Object> allParameters, String namePattern) throws Exception { int i = 0; List<TestWithParameters> children = new ArrayList<TestWithParameters>(); for (Object parametersOfSingleTest : allParameters) { children.add(createTestWithNotNormalizedParameters(namePattern, i++, parametersOfSingleTest)); } return children; } private Exception parametersMethodReturnedWrongType() throws Exception { String className = getTestClass().getName(); String methodName = getParametersMethod().getName(); String message = MessageFormat.format( "{0}.{1}() must return an Iterable of arrays.", className, methodName); return new Exception(message); } private static TestWithParameters createTestWithParameters( TestClass testClass, String pattern, int index, Object[] parameters) { String finalPattern = pattern.replaceAll("\\{index\\}", Integer.toString(index)); String name = MessageFormat.format(finalPattern, parameters); return new TestWithParameters("[" + name + "]", testClass, Arrays.asList(parameters)); } }