aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/junit/runners/Parameterized.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/junit/runners/Parameterized.java')
-rw-r--r--src/main/java/org/junit/runners/Parameterized.java405
1 files changed, 279 insertions, 126 deletions
diff --git a/src/main/java/org/junit/runners/Parameterized.java b/src/main/java/org/junit/runners/Parameterized.java
index 829c8f0..d11b66a 100644
--- a/src/main/java/org/junit/runners/Parameterized.java
+++ b/src/main/java/org/junit/runners/Parameterized.java
@@ -1,5 +1,6 @@
package org.junit.runners;
+import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
@@ -8,12 +9,18 @@ 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.InitializationError;
+import org.junit.runners.model.InvalidTestClassError;
import org.junit.runners.model.TestClass;
import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParametersFactory;
import org.junit.runners.parameterized.ParametersRunnerFactory;
@@ -24,34 +31,37 @@ import org.junit.runners.parameterized.TestWithParameters;
* 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:
+ * For example, to test the <code>+</code> operator, write:
* <pre>
* &#064;RunWith(Parameterized.class)
- * public class FibonacciTest {
- * &#064;Parameters(name= &quot;{index}: fib[{0}]={1}&quot;)
+ * public class AdditionTest {
+ * &#064;Parameters(name = &quot;{index}: {0} + {1} = {2}&quot;)
* public static Iterable&lt;Object[]&gt; data() {
- * return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 },
- * { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });
+ * return Arrays.asList(new Object[][] { { 0, 0, 0 }, { 1, 1, 2 },
+ * { 3, 2, 5 }, { 4, 3, 7 } });
* }
*
- * private int fInput;
+ * private int firstSummand;
*
- * private int fExpected;
+ * private int secondSummand;
*
- * public FibonacciTest(int input, int expected) {
- * fInput= input;
- * fExpected= expected;
+ * private int sum;
+ *
+ * public AdditionTest(int firstSummand, int secondSummand, int sum) {
+ * this.firstSummand = firstSummand;
+ * this.secondSummand = secondSummand;
+ * this.sum = sum;
* }
*
* &#064;Test
* public void test() {
- * assertEquals(fExpected, Fibonacci.compute(fInput));
+ * assertEquals(sum, firstSummand + secondSummand);
* }
* }
* </pre>
* <p>
- * Each instance of <code>FibonacciTest</code> will be constructed using the
- * two-argument constructor and the data values in the
+ * Each instance of <code>AdditionTest</code> will be constructed using the
+ * three-argument constructor and the data values in the
* <code>&#064;Parameters</code> method.
* <p>
* In order that you can easily identify the individual tests, you may provide a
@@ -69,33 +79,36 @@ import org.junit.runners.parameterized.TestWithParameters;
* </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,
+ * names like <code>[2: 3 + 2 = 5]</code>. If you don't use the name parameter,
* then the current parameter index is used as name.
* <p>
* You can also write:
* <pre>
* &#064;RunWith(Parameterized.class)
- * public class FibonacciTest {
- * &#064;Parameters
- * public static Iterable&lt;Object[]&gt; data() {
- * return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 },
- * { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });
- * }
- *
- * &#064;Parameter(0)
- * public int fInput;
+ * public class AdditionTest {
+ * &#064;Parameters(name = &quot;{index}: {0} + {1} = {2}&quot;)
+ * public static Iterable&lt;Object[]&gt; data() {
+ * return Arrays.asList(new Object[][] { { 0, 0, 0 }, { 1, 1, 2 },
+ * { 3, 2, 5 }, { 4, 3, 7 } });
+ * }
+ *
+ * &#064;Parameter(0)
+ * public int firstSummand;
*
- * &#064;Parameter(1)
- * public int fExpected;
+ * &#064;Parameter(1)
+ * public int secondSummand;
*
- * &#064;Test
- * public void test() {
- * assertEquals(fExpected, Fibonacci.compute(fInput));
- * }
+ * &#064;Parameter(2)
+ * public int sum;
+ *
+ * &#064;Test
+ * public void test() {
+ * assertEquals(sum, firstSummand + secondSummand);
+ * }
* }
* </pre>
* <p>
- * Each instance of <code>FibonacciTest</code> will be constructed with the default constructor
+ * Each instance of <code>AdditionTest</code> will be constructed with the default constructor
* and fields annotated by <code>&#064;Parameter</code> will be initialized
* with the data values in the <code>&#064;Parameters</code> method.
*
@@ -105,8 +118,7 @@ import org.junit.runners.parameterized.TestWithParameters;
* <pre>
* &#064;Parameters
* public static Object[][] data() {
- * return new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 },
- * { 5, 5 }, { 6, 8 } };
+ * return new Object[][] { { 0, 0, 0 }, { 1, 1, 2 }, { 3, 2, 5 }, { 4, 3, 7 } } };
* }
* </pre>
*
@@ -130,6 +142,19 @@ import org.junit.runners.parameterized.TestWithParameters;
* }
* </pre>
*
+ * <h3>Executing code before/after executing tests for specific parameters</h3>
+ * <p>
+ * 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.
+ * <pre>
+ * &#064;BeforeParam
+ * public static void beforeTestsForParameter(String onlyParameter) {
+ * System.out.println("Testing " + onlyParameter);
+ * }
+ * </pre>
+ *
* <h3>Create different runners</h3>
* <p>
* By default the {@code Parameterized} runner creates a slightly modified
@@ -141,7 +166,7 @@ import org.junit.runners.parameterized.TestWithParameters;
* The factory must have a public zero-arg constructor.
*
* <pre>
- * public class YourRunnerFactory implements ParameterizedRunnerFactory {
+ * public class YourRunnerFactory implements ParametersRunnerFactory {
* public Runner createRunnerForTestWithParameters(TestWithParameters test)
* throws InitializationError {
* return YourRunner(test);
@@ -160,6 +185,21 @@ import org.junit.runners.parameterized.TestWithParameters;
* }
* </pre>
*
+ * <h3>Avoid creating parameters</h3>
+ * <p>With {@link org.junit.Assume assumptions} you can dynamically skip tests.
+ * Assumptions are also supported by the <code>&#064;Parameters</code> 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.
+ * <pre>
+ * &#064;Parameters
+ * public static Iterable&lt;? extends Object&gt; data() {
+ * String os = System.getProperty("os.name").toLowerCase()
+ * Assume.assumeTrue(os.contains("win"));
+ * return Arrays.asList(&quot;first test&quot;, &quot;second test&quot;);
+ * }
+ * </pre>
* @since 4.0
*/
public class Parameterized extends Suite {
@@ -170,7 +210,7 @@ public class Parameterized extends Suite {
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
- public static @interface Parameters {
+ 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
@@ -201,7 +241,7 @@ public class Parameterized extends Suite {
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
- public static @interface Parameter {
+ public @interface Parameter {
/**
* Method that returns the index of the parameter in the array
* returned by the method annotated by <code>Parameters</code>.
@@ -230,122 +270,235 @@ public class Parameterized extends Suite {
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();
+ /**
+ * 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 {
+ }
- private final List<Runner> runners;
+ /**
+ * 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 {
- super(klass, NO_RUNNERS);
- ParametersRunnerFactory runnerFactory = getParametersRunnerFactory(
- klass);
- Parameters parameters = getParametersMethod().getAnnotation(
- Parameters.class);
- runners = Collections.unmodifiableList(createRunnersForParameters(
- allParameters(), parameters.name(), runnerFactory));
+ this(klass, new RunnersFactory(klass));
}
- 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();
- }
+ private Parameterized(Class<?> klass, RunnersFactory runnersFactory) throws Exception {
+ super(klass, runnersFactory.createRunners());
+ validateBeforeParamAndAfterParamMethods(runnersFactory.parameterCount);
}
- @Override
- protected List<Runner> getChildren() {
- return runners;
+ private void validateBeforeParamAndAfterParamMethods(Integer parameterCount)
+ throws InvalidTestClassError {
+ List<Throwable> errors = new ArrayList<Throwable>();
+ validatePublicStaticVoidMethods(Parameterized.BeforeParam.class, parameterCount, errors);
+ validatePublicStaticVoidMethods(Parameterized.AfterParam.class, parameterCount, errors);
+ if (!errors.isEmpty()) {
+ throw new InvalidTestClassError(getTestClass().getJavaClass(), errors);
+ }
}
- 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);
+ private void validatePublicStaticVoidMethods(
+ Class<? extends Annotation> annotation, Integer parameterCount,
+ List<Throwable> errors) {
+ List<FrameworkMethod> 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)"));
+ }
+ }
+ }
}
- @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 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 FrameworkMethod getParametersMethod() throws Exception {
- List<FrameworkMethod> methods = getTestClass().getAnnotatedMethods(
- Parameters.class);
- for (FrameworkMethod each : methods) {
- if (each.isStatic() && each.isPublic()) {
- return each;
+ private static class RunnersFactory {
+ private static final ParametersRunnerFactory DEFAULT_FACTORY = new BlockJUnit4ClassRunnerWithParametersFactory();
+
+ private final TestClass testClass;
+ private final FrameworkMethod parametersMethod;
+ private final List<Object> allParameters;
+ private final int parameterCount;
+ private final Runner runnerOverride;
+
+ private RunnersFactory(Class<?> klass) throws Throwable {
+ testClass = new TestClass(klass);
+ parametersMethod = getParametersMethod(testClass);
+ List<Object> 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;
}
- throw new Exception("No public static parameters method on class "
- + getTestClass().getName());
- }
+ private List<Runner> 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 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));
+ private ParametersRunnerFactory getParametersRunnerFactory()
+ throws InstantiationException, IllegalAccessException {
+ UseParametersRunnerFactory annotation = testClass
+ .getAnnotation(UseParametersRunnerFactory.class);
+ if (annotation == null) {
+ return DEFAULT_FACTORY;
+ } else {
+ Class<? extends ParametersRunnerFactory> factoryClass = annotation
+ .value();
+ return factoryClass.newInstance();
}
- 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));
+ private TestWithParameters createTestWithNotNormalizedParameters(
+ String pattern, int index, Object parametersOrSingleParameter) {
+ Object[] parameters = normalizeParameters(parametersOrSingleParameter);
+ return createTestWithParameters(testClass, pattern, index, parameters);
}
- 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 Object[] normalizeParameters(Object parametersOrSingleParameter) {
+ return (parametersOrSingleParameter instanceof Object[]) ? (Object[]) parametersOrSingleParameter
+ : new Object[] { parametersOrSingleParameter };
+ }
- 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));
+ @SuppressWarnings("unchecked")
+ private static List<Object> allParameters(
+ TestClass testClass, FrameworkMethod parametersMethod) throws Throwable {
+ Object parameters = parametersMethod.invokeExplosively(null);
+ if (parameters instanceof List) {
+ return (List<Object>) parameters;
+ } else if (parameters instanceof Collection) {
+ return new ArrayList<Object>((Collection<Object>) parameters);
+ } else if (parameters instanceof Iterable) {
+ List<Object> result = new ArrayList<Object>();
+ for (Object entry : ((Iterable<Object>) 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<FrameworkMethod> 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<Runner> createRunnersForParameters(
+ Iterable<Object> allParameters, String namePattern,
+ ParametersRunnerFactory runnerFactory) throws 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(testClass, parametersMethod);
+ }
+ }
+
+ 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 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));
+ }
}
}