1 package org.junit.runners; 2 3 import java.lang.annotation.Annotation; 4 import java.lang.annotation.ElementType; 5 import java.lang.annotation.Inherited; 6 import java.lang.annotation.Retention; 7 import java.lang.annotation.RetentionPolicy; 8 import java.lang.annotation.Target; 9 import java.text.MessageFormat; 10 import java.util.ArrayList; 11 import java.util.Arrays; 12 import java.util.Collection; 13 import java.util.Collections; 14 import java.util.List; 15 16 import org.junit.internal.AssumptionViolatedException; 17 import org.junit.runner.Description; 18 import org.junit.runner.Result; 19 import org.junit.runner.Runner; 20 import org.junit.runner.notification.Failure; 21 import org.junit.runner.notification.RunNotifier; 22 import org.junit.runners.model.FrameworkMethod; 23 import org.junit.runners.model.InvalidTestClassError; 24 import org.junit.runners.model.TestClass; 25 import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParametersFactory; 26 import org.junit.runners.parameterized.ParametersRunnerFactory; 27 import org.junit.runners.parameterized.TestWithParameters; 28 29 /** 30 * The custom runner <code>Parameterized</code> implements parameterized tests. 31 * When running a parameterized test class, instances are created for the 32 * cross-product of the test methods and the test data elements. 33 * <p> 34 * For example, to test the <code>+</code> operator, write: 35 * <pre> 36 * @RunWith(Parameterized.class) 37 * public class AdditionTest { 38 * @Parameters(name = "{index}: {0} + {1} = {2}") 39 * public static Iterable<Object[]> data() { 40 * return Arrays.asList(new Object[][] { { 0, 0, 0 }, { 1, 1, 2 }, 41 * { 3, 2, 5 }, { 4, 3, 7 } }); 42 * } 43 * 44 * private int firstSummand; 45 * 46 * private int secondSummand; 47 * 48 * private int sum; 49 * 50 * public AdditionTest(int firstSummand, int secondSummand, int sum) { 51 * this.firstSummand = firstSummand; 52 * this.secondSummand = secondSummand; 53 * this.sum = sum; 54 * } 55 * 56 * @Test 57 * public void test() { 58 * assertEquals(sum, firstSummand + secondSummand); 59 * } 60 * } 61 * </pre> 62 * <p> 63 * Each instance of <code>AdditionTest</code> will be constructed using the 64 * three-argument constructor and the data values in the 65 * <code>@Parameters</code> method. 66 * <p> 67 * In order that you can easily identify the individual tests, you may provide a 68 * name for the <code>@Parameters</code> annotation. This name is allowed 69 * to contain placeholders, which are replaced at runtime. The placeholders are 70 * <dl> 71 * <dt>{index}</dt> 72 * <dd>the current parameter index</dd> 73 * <dt>{0}</dt> 74 * <dd>the first parameter value</dd> 75 * <dt>{1}</dt> 76 * <dd>the second parameter value</dd> 77 * <dt>...</dt> 78 * <dd>...</dd> 79 * </dl> 80 * <p> 81 * In the example given above, the <code>Parameterized</code> runner creates 82 * names like <code>[2: 3 + 2 = 5]</code>. If you don't use the name parameter, 83 * then the current parameter index is used as name. 84 * <p> 85 * You can also write: 86 * <pre> 87 * @RunWith(Parameterized.class) 88 * public class AdditionTest { 89 * @Parameters(name = "{index}: {0} + {1} = {2}") 90 * public static Iterable<Object[]> data() { 91 * return Arrays.asList(new Object[][] { { 0, 0, 0 }, { 1, 1, 2 }, 92 * { 3, 2, 5 }, { 4, 3, 7 } }); 93 * } 94 * 95 * @Parameter(0) 96 * public int firstSummand; 97 * 98 * @Parameter(1) 99 * public int secondSummand; 100 * 101 * @Parameter(2) 102 * public int sum; 103 * 104 * @Test 105 * public void test() { 106 * assertEquals(sum, firstSummand + secondSummand); 107 * } 108 * } 109 * </pre> 110 * <p> 111 * Each instance of <code>AdditionTest</code> will be constructed with the default constructor 112 * and fields annotated by <code>@Parameter</code> will be initialized 113 * with the data values in the <code>@Parameters</code> method. 114 * 115 * <p> 116 * The parameters can be provided as an array, too: 117 * 118 * <pre> 119 * @Parameters 120 * public static Object[][] data() { 121 * return new Object[][] { { 0, 0, 0 }, { 1, 1, 2 }, { 3, 2, 5 }, { 4, 3, 7 } } }; 122 * } 123 * </pre> 124 * 125 * <h3>Tests with single parameter</h3> 126 * <p> 127 * If your test needs a single parameter only, you don't have to wrap it with an 128 * array. Instead you can provide an <code>Iterable</code> or an array of 129 * objects. 130 * <pre> 131 * @Parameters 132 * public static Iterable<? extends Object> data() { 133 * return Arrays.asList("first test", "second test"); 134 * } 135 * </pre> 136 * <p> 137 * or 138 * <pre> 139 * @Parameters 140 * public static Object[] data() { 141 * return new Object[] { "first test", "second test" }; 142 * } 143 * </pre> 144 * 145 * <h3>Executing code before/after executing tests for specific parameters</h3> 146 * <p> 147 * If your test needs to perform some preparation or cleanup based on the 148 * parameters, this can be done by adding public static methods annotated with 149 * {@code @BeforeParam}/{@code @AfterParam}. Such methods should either have no 150 * parameters or the same parameters as the test. 151 * <pre> 152 * @BeforeParam 153 * public static void beforeTestsForParameter(String onlyParameter) { 154 * System.out.println("Testing " + onlyParameter); 155 * } 156 * </pre> 157 * 158 * <h3>Create different runners</h3> 159 * <p> 160 * By default the {@code Parameterized} runner creates a slightly modified 161 * {@link BlockJUnit4ClassRunner} for each set of parameters. You can build an 162 * own {@code Parameterized} runner that creates another runner for each set of 163 * parameters. Therefore you have to build a {@link ParametersRunnerFactory} 164 * that creates a runner for each {@link TestWithParameters}. ( 165 * {@code TestWithParameters} are bundling the parameters and the test name.) 166 * The factory must have a public zero-arg constructor. 167 * 168 * <pre> 169 * public class YourRunnerFactory implements ParametersRunnerFactory { 170 * public Runner createRunnerForTestWithParameters(TestWithParameters test) 171 * throws InitializationError { 172 * return YourRunner(test); 173 * } 174 * } 175 * </pre> 176 * <p> 177 * Use the {@link UseParametersRunnerFactory} to tell the {@code Parameterized} 178 * runner that it should use your factory. 179 * 180 * <pre> 181 * @RunWith(Parameterized.class) 182 * @UseParametersRunnerFactory(YourRunnerFactory.class) 183 * public class YourTest { 184 * ... 185 * } 186 * </pre> 187 * 188 * <h3>Avoid creating parameters</h3> 189 * <p>With {@link org.junit.Assume assumptions} you can dynamically skip tests. 190 * Assumptions are also supported by the <code>@Parameters</code> method. 191 * Creating parameters is stopped when the assumption fails and none of the 192 * tests in the test class is executed. JUnit reports a 193 * {@link Result#getAssumptionFailureCount() single assumption failure} for the 194 * whole test class in this case. 195 * <pre> 196 * @Parameters 197 * public static Iterable<? extends Object> data() { 198 * String os = System.getProperty("os.name").toLowerCase() 199 * Assume.assumeTrue(os.contains("win")); 200 * return Arrays.asList("first test", "second test"); 201 * } 202 * </pre> 203 * @since 4.0 204 */ 205 public class Parameterized extends Suite { 206 /** 207 * Annotation for a method which provides parameters to be injected into the 208 * test class constructor by <code>Parameterized</code>. The method has to 209 * be public and static. 210 */ 211 @Retention(RetentionPolicy.RUNTIME) 212 @Target(ElementType.METHOD) 213 public @interface Parameters { 214 /** 215 * Optional pattern to derive the test's name from the parameters. Use 216 * numbers in braces to refer to the parameters or the additional data 217 * as follows: 218 * <pre> 219 * {index} - the current parameter index 220 * {0} - the first parameter value 221 * {1} - the second parameter value 222 * etc... 223 * </pre> 224 * <p> 225 * Default value is "{index}" for compatibility with previous JUnit 226 * versions. 227 * 228 * @return {@link MessageFormat} pattern string, except the index 229 * placeholder. 230 * @see MessageFormat 231 */ name()232 String name() default "{index}"; 233 } 234 235 /** 236 * Annotation for fields of the test class which will be initialized by the 237 * method annotated by <code>Parameters</code>. 238 * By using directly this annotation, the test class constructor isn't needed. 239 * Index range must start at 0. 240 * Default value is 0. 241 */ 242 @Retention(RetentionPolicy.RUNTIME) 243 @Target(ElementType.FIELD) 244 public @interface Parameter { 245 /** 246 * Method that returns the index of the parameter in the array 247 * returned by the method annotated by <code>Parameters</code>. 248 * Index range must start at 0. 249 * Default value is 0. 250 * 251 * @return the index of the parameter. 252 */ value()253 int value() default 0; 254 } 255 256 /** 257 * Add this annotation to your test class if you want to generate a special 258 * runner. You have to specify a {@link ParametersRunnerFactory} class that 259 * creates such runners. The factory must have a public zero-arg 260 * constructor. 261 */ 262 @Retention(RetentionPolicy.RUNTIME) 263 @Inherited 264 @Target(ElementType.TYPE) 265 public @interface UseParametersRunnerFactory { 266 /** 267 * @return a {@link ParametersRunnerFactory} class (must have a default 268 * constructor) 269 */ value()270 Class<? extends ParametersRunnerFactory> value() default BlockJUnit4ClassRunnerWithParametersFactory.class; 271 } 272 273 /** 274 * Annotation for {@code public static void} methods which should be executed before 275 * evaluating tests with particular parameters. 276 * 277 * @see org.junit.BeforeClass 278 * @see org.junit.Before 279 * @since 4.13 280 */ 281 @Retention(RetentionPolicy.RUNTIME) 282 @Target(ElementType.METHOD) 283 public @interface BeforeParam { 284 } 285 286 /** 287 * Annotation for {@code public static void} methods which should be executed after 288 * evaluating tests with particular parameters. 289 * 290 * @see org.junit.AfterClass 291 * @see org.junit.After 292 * @since 4.13 293 */ 294 @Retention(RetentionPolicy.RUNTIME) 295 @Target(ElementType.METHOD) 296 public @interface AfterParam { 297 } 298 299 /** 300 * Only called reflectively. Do not use programmatically. 301 */ Parameterized(Class<?> klass)302 public Parameterized(Class<?> klass) throws Throwable { 303 this(klass, new RunnersFactory(klass)); 304 } 305 Parameterized(Class<?> klass, RunnersFactory runnersFactory)306 private Parameterized(Class<?> klass, RunnersFactory runnersFactory) throws Exception { 307 super(klass, runnersFactory.createRunners()); 308 validateBeforeParamAndAfterParamMethods(runnersFactory.parameterCount); 309 } 310 validateBeforeParamAndAfterParamMethods(Integer parameterCount)311 private void validateBeforeParamAndAfterParamMethods(Integer parameterCount) 312 throws InvalidTestClassError { 313 List<Throwable> errors = new ArrayList<Throwable>(); 314 validatePublicStaticVoidMethods(Parameterized.BeforeParam.class, parameterCount, errors); 315 validatePublicStaticVoidMethods(Parameterized.AfterParam.class, parameterCount, errors); 316 if (!errors.isEmpty()) { 317 throw new InvalidTestClassError(getTestClass().getJavaClass(), errors); 318 } 319 } 320 validatePublicStaticVoidMethods( Class<? extends Annotation> annotation, Integer parameterCount, List<Throwable> errors)321 private void validatePublicStaticVoidMethods( 322 Class<? extends Annotation> annotation, Integer parameterCount, 323 List<Throwable> errors) { 324 List<FrameworkMethod> methods = getTestClass().getAnnotatedMethods(annotation); 325 for (FrameworkMethod fm : methods) { 326 fm.validatePublicVoid(true, errors); 327 if (parameterCount != null) { 328 int methodParameterCount = fm.getMethod().getParameterTypes().length; 329 if (methodParameterCount != 0 && methodParameterCount != parameterCount) { 330 errors.add(new Exception("Method " + fm.getName() 331 + "() should have 0 or " + parameterCount + " parameter(s)")); 332 } 333 } 334 } 335 } 336 337 private static class AssumptionViolationRunner extends Runner { 338 private final Description description; 339 private final AssumptionViolatedException exception; 340 AssumptionViolationRunner(TestClass testClass, String methodName, AssumptionViolatedException exception)341 AssumptionViolationRunner(TestClass testClass, String methodName, 342 AssumptionViolatedException exception) { 343 this.description = Description 344 .createTestDescription(testClass.getJavaClass(), 345 methodName + "() assumption violation"); 346 this.exception = exception; 347 } 348 349 @Override getDescription()350 public Description getDescription() { 351 return description; 352 } 353 354 @Override run(RunNotifier notifier)355 public void run(RunNotifier notifier) { 356 notifier.fireTestAssumptionFailed(new Failure(description, exception)); 357 } 358 } 359 360 private static class RunnersFactory { 361 private static final ParametersRunnerFactory DEFAULT_FACTORY = new BlockJUnit4ClassRunnerWithParametersFactory(); 362 363 private final TestClass testClass; 364 private final FrameworkMethod parametersMethod; 365 private final List<Object> allParameters; 366 private final int parameterCount; 367 private final Runner runnerOverride; 368 RunnersFactory(Class<?> klass)369 private RunnersFactory(Class<?> klass) throws Throwable { 370 testClass = new TestClass(klass); 371 parametersMethod = getParametersMethod(testClass); 372 List<Object> allParametersResult; 373 AssumptionViolationRunner assumptionViolationRunner = null; 374 try { 375 allParametersResult = allParameters(testClass, parametersMethod); 376 } catch (AssumptionViolatedException e) { 377 allParametersResult = Collections.emptyList(); 378 assumptionViolationRunner = new AssumptionViolationRunner(testClass, 379 parametersMethod.getName(), e); 380 } 381 allParameters = allParametersResult; 382 runnerOverride = assumptionViolationRunner; 383 parameterCount = 384 allParameters.isEmpty() ? 0 : normalizeParameters(allParameters.get(0)).length; 385 } 386 createRunners()387 private List<Runner> createRunners() throws Exception { 388 if (runnerOverride != null) { 389 return Collections.singletonList(runnerOverride); 390 } 391 Parameters parameters = parametersMethod.getAnnotation(Parameters.class); 392 return Collections.unmodifiableList(createRunnersForParameters( 393 allParameters, parameters.name(), 394 getParametersRunnerFactory())); 395 } 396 getParametersRunnerFactory()397 private ParametersRunnerFactory getParametersRunnerFactory() 398 throws InstantiationException, IllegalAccessException { 399 UseParametersRunnerFactory annotation = testClass 400 .getAnnotation(UseParametersRunnerFactory.class); 401 if (annotation == null) { 402 return DEFAULT_FACTORY; 403 } else { 404 Class<? extends ParametersRunnerFactory> factoryClass = annotation 405 .value(); 406 return factoryClass.newInstance(); 407 } 408 } 409 createTestWithNotNormalizedParameters( String pattern, int index, Object parametersOrSingleParameter)410 private TestWithParameters createTestWithNotNormalizedParameters( 411 String pattern, int index, Object parametersOrSingleParameter) { 412 Object[] parameters = normalizeParameters(parametersOrSingleParameter); 413 return createTestWithParameters(testClass, pattern, index, parameters); 414 } 415 normalizeParameters(Object parametersOrSingleParameter)416 private static Object[] normalizeParameters(Object parametersOrSingleParameter) { 417 return (parametersOrSingleParameter instanceof Object[]) ? (Object[]) parametersOrSingleParameter 418 : new Object[] { parametersOrSingleParameter }; 419 } 420 421 @SuppressWarnings("unchecked") allParameters( TestClass testClass, FrameworkMethod parametersMethod)422 private static List<Object> allParameters( 423 TestClass testClass, FrameworkMethod parametersMethod) throws Throwable { 424 Object parameters = parametersMethod.invokeExplosively(null); 425 if (parameters instanceof List) { 426 return (List<Object>) parameters; 427 } else if (parameters instanceof Collection) { 428 return new ArrayList<Object>((Collection<Object>) parameters); 429 } else if (parameters instanceof Iterable) { 430 List<Object> result = new ArrayList<Object>(); 431 for (Object entry : ((Iterable<Object>) parameters)) { 432 result.add(entry); 433 } 434 return result; 435 } else if (parameters instanceof Object[]) { 436 return Arrays.asList((Object[]) parameters); 437 } else { 438 throw parametersMethodReturnedWrongType(testClass, parametersMethod); 439 } 440 } 441 getParametersMethod(TestClass testClass)442 private static FrameworkMethod getParametersMethod(TestClass testClass) throws Exception { 443 List<FrameworkMethod> methods = testClass 444 .getAnnotatedMethods(Parameters.class); 445 for (FrameworkMethod each : methods) { 446 if (each.isStatic() && each.isPublic()) { 447 return each; 448 } 449 } 450 451 throw new Exception("No public static parameters method on class " 452 + testClass.getName()); 453 } 454 createRunnersForParameters( Iterable<Object> allParameters, String namePattern, ParametersRunnerFactory runnerFactory)455 private List<Runner> createRunnersForParameters( 456 Iterable<Object> allParameters, String namePattern, 457 ParametersRunnerFactory runnerFactory) throws Exception { 458 try { 459 List<TestWithParameters> tests = createTestsForParameters( 460 allParameters, namePattern); 461 List<Runner> runners = new ArrayList<Runner>(); 462 for (TestWithParameters test : tests) { 463 runners.add(runnerFactory 464 .createRunnerForTestWithParameters(test)); 465 } 466 return runners; 467 } catch (ClassCastException e) { 468 throw parametersMethodReturnedWrongType(testClass, parametersMethod); 469 } 470 } 471 createTestsForParameters( Iterable<Object> allParameters, String namePattern)472 private List<TestWithParameters> createTestsForParameters( 473 Iterable<Object> allParameters, String namePattern) 474 throws Exception { 475 int i = 0; 476 List<TestWithParameters> children = new ArrayList<TestWithParameters>(); 477 for (Object parametersOfSingleTest : allParameters) { 478 children.add(createTestWithNotNormalizedParameters(namePattern, 479 i++, parametersOfSingleTest)); 480 } 481 return children; 482 } 483 parametersMethodReturnedWrongType( TestClass testClass, FrameworkMethod parametersMethod)484 private static Exception parametersMethodReturnedWrongType( 485 TestClass testClass, FrameworkMethod parametersMethod) throws Exception { 486 String className = testClass.getName(); 487 String methodName = parametersMethod.getName(); 488 String message = MessageFormat.format( 489 "{0}.{1}() must return an Iterable of arrays.", className, 490 methodName); 491 return new Exception(message); 492 } 493 createTestWithParameters( TestClass testClass, String pattern, int index, Object[] parameters)494 private TestWithParameters createTestWithParameters( 495 TestClass testClass, String pattern, int index, 496 Object[] parameters) { 497 String finalPattern = pattern.replaceAll("\\{index\\}", 498 Integer.toString(index)); 499 String name = MessageFormat.format(finalPattern, parameters); 500 return new TestWithParameters("[" + name + "]", testClass, 501 Arrays.asList(parameters)); 502 } 503 } 504 } 505