diff options
Diffstat (limited to 'src/main/java/org/junit/runners/Parameterized.java')
-rw-r--r-- | src/main/java/org/junit/runners/Parameterized.java | 405 |
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> * @RunWith(Parameterized.class) - * public class FibonacciTest { - * @Parameters(name= "{index}: fib[{0}]={1}") + * public class AdditionTest { + * @Parameters(name = "{index}: {0} + {1} = {2}") * public static Iterable<Object[]> 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; * } * * @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>@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> * @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; + * 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 fExpected; + * @Parameter(1) + * public int secondSummand; * - * @Test - * public void test() { - * assertEquals(fExpected, Fibonacci.compute(fInput)); - * } + * @Parameter(2) + * public int sum; + * + * @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>@Parameter</code> will be initialized * with the data values in the <code>@Parameters</code> method. * @@ -105,8 +118,7 @@ import org.junit.runners.parameterized.TestWithParameters; * <pre> * @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> + * @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>@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> + * @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"); + * } + * </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)); + } } } |