1 package org.junit.runners; 2 3 import java.lang.annotation.ElementType; 4 import java.lang.annotation.Inherited; 5 import java.lang.annotation.Retention; 6 import java.lang.annotation.RetentionPolicy; 7 import java.lang.annotation.Target; 8 import java.text.MessageFormat; 9 import java.util.ArrayList; 10 import java.util.Arrays; 11 import java.util.Collections; 12 import java.util.List; 13 14 import org.junit.runner.Runner; 15 import org.junit.runners.model.FrameworkMethod; 16 import org.junit.runners.model.InitializationError; 17 import org.junit.runners.model.TestClass; 18 import org.junit.runners.parameterized.BlockJUnit4ClassRunnerWithParametersFactory; 19 import org.junit.runners.parameterized.ParametersRunnerFactory; 20 import org.junit.runners.parameterized.TestWithParameters; 21 22 /** 23 * The custom runner <code>Parameterized</code> implements parameterized tests. 24 * When running a parameterized test class, instances are created for the 25 * cross-product of the test methods and the test data elements. 26 * <p> 27 * For example, to test a Fibonacci function, write: 28 * <pre> 29 * @RunWith(Parameterized.class) 30 * public class FibonacciTest { 31 * @Parameters(name= "{index}: fib[{0}]={1}") 32 * public static Iterable<Object[]> data() { 33 * return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 }, 34 * { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } }); 35 * } 36 * 37 * private int fInput; 38 * 39 * private int fExpected; 40 * 41 * public FibonacciTest(int input, int expected) { 42 * fInput= input; 43 * fExpected= expected; 44 * } 45 * 46 * @Test 47 * public void test() { 48 * assertEquals(fExpected, Fibonacci.compute(fInput)); 49 * } 50 * } 51 * </pre> 52 * <p> 53 * Each instance of <code>FibonacciTest</code> will be constructed using the 54 * two-argument constructor and the data values in the 55 * <code>@Parameters</code> method. 56 * <p> 57 * In order that you can easily identify the individual tests, you may provide a 58 * name for the <code>@Parameters</code> annotation. This name is allowed 59 * to contain placeholders, which are replaced at runtime. The placeholders are 60 * <dl> 61 * <dt>{index}</dt> 62 * <dd>the current parameter index</dd> 63 * <dt>{0}</dt> 64 * <dd>the first parameter value</dd> 65 * <dt>{1}</dt> 66 * <dd>the second parameter value</dd> 67 * <dt>...</dt> 68 * <dd>...</dd> 69 * </dl> 70 * <p> 71 * In the example given above, the <code>Parameterized</code> runner creates 72 * names like <code>[1: fib(3)=2]</code>. If you don't use the name parameter, 73 * then the current parameter index is used as name. 74 * <p> 75 * You can also write: 76 * <pre> 77 * @RunWith(Parameterized.class) 78 * public class FibonacciTest { 79 * @Parameters 80 * public static Iterable<Object[]> data() { 81 * return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 }, 82 * { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } }); 83 * } 84 * 85 * @Parameter(0) 86 * public int fInput; 87 * 88 * @Parameter(1) 89 * public int fExpected; 90 * 91 * @Test 92 * public void test() { 93 * assertEquals(fExpected, Fibonacci.compute(fInput)); 94 * } 95 * } 96 * </pre> 97 * <p> 98 * Each instance of <code>FibonacciTest</code> will be constructed with the default constructor 99 * and fields annotated by <code>@Parameter</code> will be initialized 100 * with the data values in the <code>@Parameters</code> method. 101 * 102 * <p> 103 * The parameters can be provided as an array, too: 104 * 105 * <pre> 106 * @Parameters 107 * public static Object[][] data() { 108 * return new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, 109 * { 5, 5 }, { 6, 8 } }; 110 * } 111 * </pre> 112 * 113 * <h3>Tests with single parameter</h3> 114 * <p> 115 * If your test needs a single parameter only, you don't have to wrap it with an 116 * array. Instead you can provide an <code>Iterable</code> or an array of 117 * objects. 118 * <pre> 119 * @Parameters 120 * public static Iterable<? extends Object> data() { 121 * return Arrays.asList("first test", "second test"); 122 * } 123 * </pre> 124 * <p> 125 * or 126 * <pre> 127 * @Parameters 128 * public static Object[] data() { 129 * return new Object[] { "first test", "second test" }; 130 * } 131 * </pre> 132 * 133 * <h3>Create different runners</h3> 134 * <p> 135 * By default the {@code Parameterized} runner creates a slightly modified 136 * {@link BlockJUnit4ClassRunner} for each set of parameters. You can build an 137 * own {@code Parameterized} runner that creates another runner for each set of 138 * parameters. Therefore you have to build a {@link ParametersRunnerFactory} 139 * that creates a runner for each {@link TestWithParameters}. ( 140 * {@code TestWithParameters} are bundling the parameters and the test name.) 141 * The factory must have a public zero-arg constructor. 142 * 143 * <pre> 144 * public class YourRunnerFactory implements ParameterizedRunnerFactory { 145 * public Runner createRunnerForTestWithParameters(TestWithParameters test) 146 * throws InitializationError { 147 * return YourRunner(test); 148 * } 149 * } 150 * </pre> 151 * <p> 152 * Use the {@link UseParametersRunnerFactory} to tell the {@code Parameterized} 153 * runner that it should use your factory. 154 * 155 * <pre> 156 * @RunWith(Parameterized.class) 157 * @UseParametersRunnerFactory(YourRunnerFactory.class) 158 * public class YourTest { 159 * ... 160 * } 161 * </pre> 162 * 163 * @since 4.0 164 */ 165 public class Parameterized extends Suite { 166 /** 167 * Annotation for a method which provides parameters to be injected into the 168 * test class constructor by <code>Parameterized</code>. The method has to 169 * be public and static. 170 */ 171 @Retention(RetentionPolicy.RUNTIME) 172 @Target(ElementType.METHOD) 173 public static @interface Parameters { 174 /** 175 * Optional pattern to derive the test's name from the parameters. Use 176 * numbers in braces to refer to the parameters or the additional data 177 * as follows: 178 * <pre> 179 * {index} - the current parameter index 180 * {0} - the first parameter value 181 * {1} - the second parameter value 182 * etc... 183 * </pre> 184 * <p> 185 * Default value is "{index}" for compatibility with previous JUnit 186 * versions. 187 * 188 * @return {@link MessageFormat} pattern string, except the index 189 * placeholder. 190 * @see MessageFormat 191 */ name()192 String name() default "{index}"; 193 } 194 195 /** 196 * Annotation for fields of the test class which will be initialized by the 197 * method annotated by <code>Parameters</code>. 198 * By using directly this annotation, the test class constructor isn't needed. 199 * Index range must start at 0. 200 * Default value is 0. 201 */ 202 @Retention(RetentionPolicy.RUNTIME) 203 @Target(ElementType.FIELD) 204 public static @interface Parameter { 205 /** 206 * Method that returns the index of the parameter in the array 207 * returned by the method annotated by <code>Parameters</code>. 208 * Index range must start at 0. 209 * Default value is 0. 210 * 211 * @return the index of the parameter. 212 */ value()213 int value() default 0; 214 } 215 216 /** 217 * Add this annotation to your test class if you want to generate a special 218 * runner. You have to specify a {@link ParametersRunnerFactory} class that 219 * creates such runners. The factory must have a public zero-arg 220 * constructor. 221 */ 222 @Retention(RetentionPolicy.RUNTIME) 223 @Inherited 224 @Target(ElementType.TYPE) 225 public @interface UseParametersRunnerFactory { 226 /** 227 * @return a {@link ParametersRunnerFactory} class (must have a default 228 * constructor) 229 */ value()230 Class<? extends ParametersRunnerFactory> value() default BlockJUnit4ClassRunnerWithParametersFactory.class; 231 } 232 233 private static final ParametersRunnerFactory DEFAULT_FACTORY = new BlockJUnit4ClassRunnerWithParametersFactory(); 234 235 private static final List<Runner> NO_RUNNERS = Collections.<Runner>emptyList(); 236 237 private final List<Runner> runners; 238 239 /** 240 * Only called reflectively. Do not use programmatically. 241 */ Parameterized(Class<?> klass)242 public Parameterized(Class<?> klass) throws Throwable { 243 super(klass, NO_RUNNERS); 244 ParametersRunnerFactory runnerFactory = getParametersRunnerFactory( 245 klass); 246 Parameters parameters = getParametersMethod().getAnnotation( 247 Parameters.class); 248 runners = Collections.unmodifiableList(createRunnersForParameters( 249 allParameters(), parameters.name(), runnerFactory)); 250 } 251 getParametersRunnerFactory(Class<?> klass)252 private ParametersRunnerFactory getParametersRunnerFactory(Class<?> klass) 253 throws InstantiationException, IllegalAccessException { 254 UseParametersRunnerFactory annotation = klass 255 .getAnnotation(UseParametersRunnerFactory.class); 256 if (annotation == null) { 257 return DEFAULT_FACTORY; 258 } else { 259 Class<? extends ParametersRunnerFactory> factoryClass = annotation 260 .value(); 261 return factoryClass.newInstance(); 262 } 263 } 264 265 @Override getChildren()266 protected List<Runner> getChildren() { 267 return runners; 268 } 269 createTestWithNotNormalizedParameters( String pattern, int index, Object parametersOrSingleParameter)270 private TestWithParameters createTestWithNotNormalizedParameters( 271 String pattern, int index, Object parametersOrSingleParameter) { 272 Object[] parameters= (parametersOrSingleParameter instanceof Object[]) ? (Object[]) parametersOrSingleParameter 273 : new Object[] { parametersOrSingleParameter }; 274 return createTestWithParameters(getTestClass(), pattern, index, 275 parameters); 276 } 277 278 @SuppressWarnings("unchecked") allParameters()279 private Iterable<Object> allParameters() throws Throwable { 280 Object parameters = getParametersMethod().invokeExplosively(null); 281 if (parameters instanceof Iterable) { 282 return (Iterable<Object>) parameters; 283 } else if (parameters instanceof Object[]) { 284 return Arrays.asList((Object[]) parameters); 285 } else { 286 throw parametersMethodReturnedWrongType(); 287 } 288 } 289 getParametersMethod()290 private FrameworkMethod getParametersMethod() throws Exception { 291 List<FrameworkMethod> methods = getTestClass().getAnnotatedMethods( 292 Parameters.class); 293 for (FrameworkMethod each : methods) { 294 if (each.isStatic() && each.isPublic()) { 295 return each; 296 } 297 } 298 299 throw new Exception("No public static parameters method on class " 300 + getTestClass().getName()); 301 } 302 createRunnersForParameters( Iterable<Object> allParameters, String namePattern, ParametersRunnerFactory runnerFactory)303 private List<Runner> createRunnersForParameters( 304 Iterable<Object> allParameters, String namePattern, 305 ParametersRunnerFactory runnerFactory) 306 throws InitializationError, 307 Exception { 308 try { 309 List<TestWithParameters> tests = createTestsForParameters( 310 allParameters, namePattern); 311 List<Runner> runners = new ArrayList<Runner>(); 312 for (TestWithParameters test : tests) { 313 runners.add(runnerFactory 314 .createRunnerForTestWithParameters(test)); 315 } 316 return runners; 317 } catch (ClassCastException e) { 318 throw parametersMethodReturnedWrongType(); 319 } 320 } 321 createTestsForParameters( Iterable<Object> allParameters, String namePattern)322 private List<TestWithParameters> createTestsForParameters( 323 Iterable<Object> allParameters, String namePattern) 324 throws Exception { 325 int i = 0; 326 List<TestWithParameters> children = new ArrayList<TestWithParameters>(); 327 for (Object parametersOfSingleTest : allParameters) { 328 children.add(createTestWithNotNormalizedParameters(namePattern, 329 i++, parametersOfSingleTest)); 330 } 331 return children; 332 } 333 parametersMethodReturnedWrongType()334 private Exception parametersMethodReturnedWrongType() throws Exception { 335 String className = getTestClass().getName(); 336 String methodName = getParametersMethod().getName(); 337 String message = MessageFormat.format( 338 "{0}.{1}() must return an Iterable of arrays.", 339 className, methodName); 340 return new Exception(message); 341 } 342 createTestWithParameters( TestClass testClass, String pattern, int index, Object[] parameters)343 private static TestWithParameters createTestWithParameters( 344 TestClass testClass, String pattern, int index, Object[] parameters) { 345 String finalPattern = pattern.replaceAll("\\{index\\}", 346 Integer.toString(index)); 347 String name = MessageFormat.format(finalPattern, parameters); 348 return new TestWithParameters("[" + name + "]", testClass, 349 Arrays.asList(parameters)); 350 } 351 } 352