All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.junit.runners.Parameterized Maven / Gradle / Ivy

There is a newer version: 2.12.15
Show newest version
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 Parameterized 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.
 * 

* For example, to test a Fibonacci function, write: *

 * @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));
 *     }
 * }
 * 
*

* Each instance of FibonacciTest will be constructed using the * two-argument constructor and the data values in the * @Parameters method. *

* In order that you can easily identify the individual tests, you may provide a * name for the @Parameters annotation. This name is allowed * to contain placeholders, which are replaced at runtime. The placeholders are *

*
{index}
*
the current parameter index
*
{0}
*
the first parameter value
*
{1}
*
the second parameter value
*
...
*
...
*
*

* In the example given above, the Parameterized runner creates * names like [1: fib(3)=2]. If you don't use the name parameter, * then the current parameter index is used as name. *

* You can also write: *

 * @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));
 *  }
 * }
 * 
*

* Each instance of FibonacciTest will be constructed with the default constructor * and fields annotated by @Parameter will be initialized * with the data values in the @Parameters method. * *

* The parameters can be provided as an array, too: * *

 * @Parameters
 * public static Object[][] data() {
 * 	return new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 },
 * 			{ 5, 5 }, { 6, 8 } };
 * }
 * 
* *

Tests with single parameter

*

* If your test needs a single parameter only, you don't have to wrap it with an * array. Instead you can provide an Iterable or an array of * objects. *

 * @Parameters
 * public static Iterable<? extends Object> data() {
 * 	return Arrays.asList("first test", "second test");
 * }
 * 
*

* or *

 * @Parameters
 * public static Object[] data() {
 * 	return new Object[] { "first test", "second test" };
 * }
 * 
* *

Create different runners

*

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

 * public class YourRunnerFactory implements ParameterizedRunnerFactory {
 *     public Runner createRunnerForTestWithParameters(TestWithParameters test)
 *             throws InitializationError {
 *         return YourRunner(test);
 *     }
 * }
 * 
*

* Use the {@link UseParametersRunnerFactory} to tell the {@code Parameterized} * runner that it should use your factory. * *

 * @RunWith(Parameterized.class)
 * @UseParametersRunnerFactory(YourRunnerFactory.class)
 * public class YourTest {
 *     ...
 * }
 * 
* * @since 4.0 */ public class Parameterized extends Suite { /** * Annotation for a method which provides parameters to be injected into the * test class constructor by Parameterized. 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: *
         * {index} - the current parameter index
         * {0} - the first parameter value
         * {1} - the second parameter value
         * etc...
         * 
*

* 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 Parameters. * 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 Parameters. * 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 value() default BlockJUnit4ClassRunnerWithParametersFactory.class; } private static final ParametersRunnerFactory DEFAULT_FACTORY = new BlockJUnit4ClassRunnerWithParametersFactory(); private static final List NO_RUNNERS = Collections.emptyList(); private final List 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 factoryClass = annotation .value(); return factoryClass.newInstance(); } } @Override protected List 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 allParameters() throws Throwable { Object parameters = getParametersMethod().invokeExplosively(null); if (parameters instanceof Iterable) { return (Iterable) parameters; } else if (parameters instanceof Object[]) { return Arrays.asList((Object[]) parameters); } else { throw parametersMethodReturnedWrongType(); } } private FrameworkMethod getParametersMethod() throws Exception { List 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 createRunnersForParameters( Iterable allParameters, String namePattern, ParametersRunnerFactory runnerFactory) throws InitializationError, Exception { try { List tests = createTestsForParameters( allParameters, namePattern); List runners = new ArrayList(); for (TestWithParameters test : tests) { runners.add(runnerFactory .createRunnerForTestWithParameters(test)); } return runners; } catch (ClassCastException e) { throw parametersMethodReturnedWrongType(); } } private List createTestsForParameters( Iterable allParameters, String namePattern) throws Exception { int i = 0; List children = new ArrayList(); 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)); } }