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

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

Go to download

JUnit is a unit testing framework for Java, created by Erich Gamma and Kent Beck.

There is a newer version: 4.13.2
Show newest version
package org.junit.runners;

import java.lang.annotation.Annotation;
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.Collection;
import java.util.Collections;
import java.util.List;

import org.junit.internal.AssumptionViolatedException;
import org.junit.runner.Description;
import org.junit.runner.Result;
import org.junit.runner.Runner;
import org.junit.runner.notification.Failure;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InvalidTestClassError;
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 the + operator, write: *

 * @RunWith(Parameterized.class)
 * public class AdditionTest {
 *     @Parameters(name = "{index}: {0} + {1} = {2}")
 *     public static Iterable<Object[]> data() {
 *         return Arrays.asList(new Object[][] { { 0, 0, 0 }, { 1, 1, 2 },
 *                 { 3, 2, 5 }, { 4, 3, 7 } });
 *     }
 *
 *     private int firstSummand;
 *
 *     private int secondSummand;
 *
 *     private int sum;
 *
 *     public AdditionTest(int firstSummand, int secondSummand, int sum) {
 *         this.firstSummand = firstSummand;
 *         this.secondSummand = secondSummand;
 *         this.sum = sum;
 *     }
 *
 *     @Test
 *     public void test() {
 *         assertEquals(sum, firstSummand + secondSummand);
 *     }
 * }
 * 
*

* Each instance of AdditionTest will be constructed using the * three-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 [2: 3 + 2 = 5]. 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 AdditionTest {
 *     @Parameters(name = "{index}: {0} + {1} = {2}")
 *     public static Iterable<Object[]> data() {
 *         return Arrays.asList(new Object[][] { { 0, 0, 0 }, { 1, 1, 2 },
 *                 { 3, 2, 5 }, { 4, 3, 7 } });
 *     }
 *
 *     @Parameter(0)
 *     public int firstSummand;
 *
 *     @Parameter(1)
 *     public int secondSummand;
 *
 *     @Parameter(2)
 *     public int sum;
 *
 *     @Test
 *     public void test() {
 *         assertEquals(sum, firstSummand + secondSummand);
 *     }
 * }
 * 
*

* Each instance of AdditionTest 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, 0 }, { 1, 1, 2 }, { 3, 2, 5 }, { 4, 3, 7 } } };
 * }
 * 
* *

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" };
 * }
 * 
* *

Executing code before/after executing tests for specific parameters

*

* If your test needs to perform some preparation or cleanup based on the * parameters, this can be done by adding public static methods annotated with * {@code @BeforeParam}/{@code @AfterParam}. Such methods should either have no * parameters or the same parameters as the test. *

 * @BeforeParam
 * public static void beforeTestsForParameter(String onlyParameter) {
 *     System.out.println("Testing " + onlyParameter);
 * }
 * 
* *

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 ParametersRunnerFactory {
 *     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 {
 *     ...
 * }
 * 
* *

Avoid creating parameters

*

With {@link org.junit.Assume assumptions} you can dynamically skip tests. * Assumptions are also supported by the @Parameters method. * Creating parameters is stopped when the assumption fails and none of the * tests in the test class is executed. JUnit reports a * {@link Result#getAssumptionFailureCount() single assumption failure} for the * whole test class in this case. *

 * @Parameters
 * public static Iterable<? extends Object> data() {
 * 	String os = System.getProperty("os.name").toLowerCase()
 * 	Assume.assumeTrue(os.contains("win"));
 * 	return Arrays.asList("first test", "second test");
 * }
 * 
* @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 @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 @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; } /** * Annotation for {@code public static void} methods which should be executed before * evaluating tests with particular parameters. * * @see org.junit.BeforeClass * @see org.junit.Before * @since 4.13 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface BeforeParam { } /** * Annotation for {@code public static void} methods which should be executed after * evaluating tests with particular parameters. * * @see org.junit.AfterClass * @see org.junit.After * @since 4.13 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface AfterParam { } /** * Only called reflectively. Do not use programmatically. */ public Parameterized(Class klass) throws Throwable { this(klass, new RunnersFactory(klass)); } private Parameterized(Class klass, RunnersFactory runnersFactory) throws Exception { super(klass, runnersFactory.createRunners()); validateBeforeParamAndAfterParamMethods(runnersFactory.parameterCount); } private void validateBeforeParamAndAfterParamMethods(Integer parameterCount) throws InvalidTestClassError { List errors = new ArrayList(); validatePublicStaticVoidMethods(Parameterized.BeforeParam.class, parameterCount, errors); validatePublicStaticVoidMethods(Parameterized.AfterParam.class, parameterCount, errors); if (!errors.isEmpty()) { throw new InvalidTestClassError(getTestClass().getJavaClass(), errors); } } private void validatePublicStaticVoidMethods( Class annotation, Integer parameterCount, List errors) { List methods = getTestClass().getAnnotatedMethods(annotation); for (FrameworkMethod fm : methods) { fm.validatePublicVoid(true, errors); if (parameterCount != null) { int methodParameterCount = fm.getMethod().getParameterTypes().length; if (methodParameterCount != 0 && methodParameterCount != parameterCount) { errors.add(new Exception("Method " + fm.getName() + "() should have 0 or " + parameterCount + " parameter(s)")); } } } } private static class AssumptionViolationRunner extends Runner { private final Description description; private final AssumptionViolatedException exception; AssumptionViolationRunner(TestClass testClass, String methodName, AssumptionViolatedException exception) { this.description = Description .createTestDescription(testClass.getJavaClass(), methodName + "() assumption violation"); this.exception = exception; } @Override public Description getDescription() { return description; } @Override public void run(RunNotifier notifier) { notifier.fireTestAssumptionFailed(new Failure(description, exception)); } } private static class RunnersFactory { private static final ParametersRunnerFactory DEFAULT_FACTORY = new BlockJUnit4ClassRunnerWithParametersFactory(); private final TestClass testClass; private final FrameworkMethod parametersMethod; private final List allParameters; private final int parameterCount; private final Runner runnerOverride; private RunnersFactory(Class klass) throws Throwable { testClass = new TestClass(klass); parametersMethod = getParametersMethod(testClass); List allParametersResult; AssumptionViolationRunner assumptionViolationRunner = null; try { allParametersResult = allParameters(testClass, parametersMethod); } catch (AssumptionViolatedException e) { allParametersResult = Collections.emptyList(); assumptionViolationRunner = new AssumptionViolationRunner(testClass, parametersMethod.getName(), e); } allParameters = allParametersResult; runnerOverride = assumptionViolationRunner; parameterCount = allParameters.isEmpty() ? 0 : normalizeParameters(allParameters.get(0)).length; } private List createRunners() throws Exception { if (runnerOverride != null) { return Collections.singletonList(runnerOverride); } Parameters parameters = parametersMethod.getAnnotation(Parameters.class); return Collections.unmodifiableList(createRunnersForParameters( allParameters, parameters.name(), getParametersRunnerFactory())); } private ParametersRunnerFactory getParametersRunnerFactory() throws InstantiationException, IllegalAccessException { UseParametersRunnerFactory annotation = testClass .getAnnotation(UseParametersRunnerFactory.class); if (annotation == null) { return DEFAULT_FACTORY; } else { Class factoryClass = annotation .value(); return factoryClass.newInstance(); } } private TestWithParameters createTestWithNotNormalizedParameters( String pattern, int index, Object parametersOrSingleParameter) { Object[] parameters = normalizeParameters(parametersOrSingleParameter); return createTestWithParameters(testClass, pattern, index, parameters); } private static Object[] normalizeParameters(Object parametersOrSingleParameter) { return (parametersOrSingleParameter instanceof Object[]) ? (Object[]) parametersOrSingleParameter : new Object[] { parametersOrSingleParameter }; } @SuppressWarnings("unchecked") private static List allParameters( TestClass testClass, FrameworkMethod parametersMethod) throws Throwable { Object parameters = parametersMethod.invokeExplosively(null); if (parameters instanceof List) { return (List) parameters; } else if (parameters instanceof Collection) { return new ArrayList((Collection) parameters); } else if (parameters instanceof Iterable) { List result = new ArrayList(); for (Object entry : ((Iterable) parameters)) { result.add(entry); } return result; } else if (parameters instanceof Object[]) { return Arrays.asList((Object[]) parameters); } else { throw parametersMethodReturnedWrongType(testClass, parametersMethod); } } private static FrameworkMethod getParametersMethod(TestClass testClass) throws Exception { List methods = testClass .getAnnotatedMethods(Parameters.class); for (FrameworkMethod each : methods) { if (each.isStatic() && each.isPublic()) { return each; } } throw new Exception("No public static parameters method on class " + testClass.getName()); } private List createRunnersForParameters( Iterable allParameters, String namePattern, ParametersRunnerFactory runnerFactory) throws 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(testClass, parametersMethod); } } 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 static Exception parametersMethodReturnedWrongType( TestClass testClass, FrameworkMethod parametersMethod) throws Exception { String className = testClass.getName(); String methodName = parametersMethod.getName(); String message = MessageFormat.format( "{0}.{1}() must return an Iterable of arrays.", className, methodName); return new Exception(message); } private 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)); } } }