| package org.junit.runners; |
| |
| import java.lang.annotation.ElementType; |
| import java.lang.annotation.Inherited; |
| 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 build an |
| * own {@code Parameterized} runner that creates another runner for each set of |
| * parameters. Therefore you have to build 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) |
| @Inherited |
| @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> runners; |
| |
| /** |
| * 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); |
| runners = 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 runners; |
| } |
| |
| 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)); |
| } |
| } |