001package org.junit.runners; 002 003import java.lang.annotation.Annotation; 004import java.lang.annotation.ElementType; 005import java.lang.annotation.Retention; 006import java.lang.annotation.RetentionPolicy; 007import java.lang.annotation.Target; 008import java.lang.reflect.Field; 009import java.text.MessageFormat; 010import java.util.ArrayList; 011import java.util.Collections; 012import java.util.List; 013 014import org.junit.runner.Runner; 015import org.junit.runner.notification.RunNotifier; 016import org.junit.runners.model.FrameworkField; 017import org.junit.runners.model.FrameworkMethod; 018import org.junit.runners.model.InitializationError; 019import org.junit.runners.model.Statement; 020 021/** 022 * <p> 023 * The custom runner <code>Parameterized</code> implements parameterized tests. 024 * When running a parameterized test class, instances are created for the 025 * cross-product of the test methods and the test data elements. 026 * </p> 027 * 028 * For example, to test a Fibonacci function, write: 029 * 030 * <pre> 031 * @RunWith(Parameterized.class) 032 * public class FibonacciTest { 033 * @Parameters(name= "{index}: fib[{0}]={1}") 034 * public static Iterable<Object[]> data() { 035 * return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 }, 036 * { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } }); 037 * } 038 * 039 * private int fInput; 040 * 041 * private int fExpected; 042 * 043 * public FibonacciTest(int input, int expected) { 044 * fInput= input; 045 * fExpected= expected; 046 * } 047 * 048 * @Test 049 * public void test() { 050 * assertEquals(fExpected, Fibonacci.compute(fInput)); 051 * } 052 * } 053 * </pre> 054 * 055 * <p> 056 * Each instance of <code>FibonacciTest</code> will be constructed using the 057 * two-argument constructor and the data values in the 058 * <code>@Parameters</code> method. 059 * 060 * <p> 061 * In order that you can easily identify the individual tests, you may provide a 062 * name for the <code>@Parameters</code> annotation. This name is allowed 063 * to contain placeholders, which are replaced at runtime. The placeholders are 064 * <dl> 065 * <dt>{index}</dt> 066 * <dd>the current parameter index</dd> 067 * <dt>{0}</dt> 068 * <dd>the first parameter value</dd> 069 * <dt>{1}</dt> 070 * <dd>the second parameter value</dd> 071 * <dt>...</dt> 072 * <dd></dd> 073 * </dl> 074 * In the example given above, the <code>Parameterized</code> runner creates 075 * names like <code>[1: fib(3)=2]</code>. If you don't use the name parameter, 076 * then the current parameter index is used as name. 077 * </p> 078 * 079 * You can also write: 080 * 081 * <pre> 082 * @RunWith(Parameterized.class) 083 * public class FibonacciTest { 084 * @Parameters 085 * public static Iterable<Object[]> data() { 086 * return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 }, 087 * { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } }); 088 * } 089 * 090 * @Parameter(0) 091 * public int fInput; 092 * 093 * @Parameter(1) 094 * public int fExpected; 095 * 096 * @Test 097 * public void test() { 098 * assertEquals(fExpected, Fibonacci.compute(fInput)); 099 * } 100 * } 101 * </pre> 102 * 103 * <p> 104 * Each instance of <code>FibonacciTest</code> will be constructed with the default constructor 105 * and fields annotated by <code>@Parameter</code> will be initialized 106 * with the data values in the <code>@Parameters</code> method. 107 * </p> 108 * 109 * @since 4.0 110 */ 111public class Parameterized extends Suite { 112 /** 113 * Annotation for a method which provides parameters to be injected into the 114 * test class constructor by <code>Parameterized</code> 115 */ 116 @Retention(RetentionPolicy.RUNTIME) 117 @Target(ElementType.METHOD) 118 public static @interface Parameters { 119 /** 120 * <p> 121 * Optional pattern to derive the test's name from the parameters. Use 122 * numbers in braces to refer to the parameters or the additional data 123 * as follows: 124 * </p> 125 * 126 * <pre> 127 * {index} - the current parameter index 128 * {0} - the first parameter value 129 * {1} - the second parameter value 130 * etc... 131 * </pre> 132 * <p> 133 * Default value is "{index}" for compatibility with previous JUnit 134 * versions. 135 * </p> 136 * 137 * @return {@link MessageFormat} pattern string, except the index 138 * placeholder. 139 * @see MessageFormat 140 */ 141 String name() default "{index}"; 142 } 143 144 /** 145 * Annotation for fields of the test class which will be initialized by the 146 * method annotated by <code>Parameters</code><br/> 147 * By using directly this annotation, the test class constructor isn't needed.<br/> 148 * Index range must start at 0. 149 * Default value is 0. 150 */ 151 @Retention(RetentionPolicy.RUNTIME) 152 @Target(ElementType.FIELD) 153 public static @interface Parameter { 154 /** 155 * Method that returns the index of the parameter in the array 156 * returned by the method annotated by <code>Parameters</code>.<br/> 157 * Index range must start at 0. 158 * Default value is 0. 159 * 160 * @return the index of the parameter. 161 */ 162 int value() default 0; 163 } 164 165 protected class TestClassRunnerForParameters extends BlockJUnit4ClassRunner { 166 private final Object[] fParameters; 167 168 private String fName; 169 170 protected TestClassRunnerForParameters(Class<?> type, String pattern, int index, Object[] parameters) throws InitializationError { 171 super(type); 172 173 fParameters = parameters; 174 fName = nameFor(pattern, index, parameters); 175 } 176 177 @Override 178 public Object createTest() throws Exception { 179 if (fieldsAreAnnotated()) { 180 return createTestUsingFieldInjection(); 181 } else { 182 return createTestUsingConstructorInjection(); 183 } 184 } 185 186 private Object createTestUsingConstructorInjection() throws Exception { 187 return getTestClass().getOnlyConstructor().newInstance(fParameters); 188 } 189 190 private Object createTestUsingFieldInjection() throws Exception { 191 List<FrameworkField> annotatedFieldsByParameter = getAnnotatedFieldsByParameter(); 192 if (annotatedFieldsByParameter.size() != fParameters.length) { 193 throw new Exception("Wrong number of parameters and @Parameter fields." + 194 " @Parameter fields counted: " + annotatedFieldsByParameter.size() + ", available parameters: " + fParameters.length + "."); 195 } 196 Object testClassInstance = getTestClass().getJavaClass().newInstance(); 197 for (FrameworkField each : annotatedFieldsByParameter) { 198 Field field = each.getField(); 199 Parameter annotation = field.getAnnotation(Parameter.class); 200 int index = annotation.value(); 201 try { 202 field.set(testClassInstance, fParameters[index]); 203 } catch (IllegalArgumentException iare) { 204 throw new Exception(getTestClass().getName() + ": Trying to set " + field.getName() + 205 " with the value " + fParameters[index] + 206 " that is not the right type (" + fParameters[index].getClass().getSimpleName() + " instead of " + 207 field.getType().getSimpleName() + ").", iare); 208 } 209 } 210 return testClassInstance; 211 } 212 213 protected String nameFor(String pattern, int index, Object[] parameters) { 214 String finalPattern = pattern.replaceAll("\\{index\\}", Integer.toString(index)); 215 String name = MessageFormat.format(finalPattern, parameters); 216 return "[" + name + "]"; 217 } 218 219 @Override 220 protected String getName() { 221 return fName; 222 } 223 224 @Override 225 protected String testName(FrameworkMethod method) { 226 return method.getName() + getName(); 227 } 228 229 @Override 230 protected void validateConstructor(List<Throwable> errors) { 231 validateOnlyOneConstructor(errors); 232 if (fieldsAreAnnotated()) { 233 validateZeroArgConstructor(errors); 234 } 235 } 236 237 @Override 238 protected void validateFields(List<Throwable> errors) { 239 super.validateFields(errors); 240 if (fieldsAreAnnotated()) { 241 List<FrameworkField> annotatedFieldsByParameter = getAnnotatedFieldsByParameter(); 242 int[] usedIndices = new int[annotatedFieldsByParameter.size()]; 243 for (FrameworkField each : annotatedFieldsByParameter) { 244 int index = each.getField().getAnnotation(Parameter.class).value(); 245 if (index < 0 || index > annotatedFieldsByParameter.size() - 1) { 246 errors.add( 247 new Exception("Invalid @Parameter value: " + index + ". @Parameter fields counted: " + 248 annotatedFieldsByParameter.size() + ". Please use an index between 0 and " + 249 (annotatedFieldsByParameter.size() - 1) + ".") 250 ); 251 } else { 252 usedIndices[index]++; 253 } 254 } 255 for (int index = 0; index < usedIndices.length; index++) { 256 int numberOfUse = usedIndices[index]; 257 if (numberOfUse == 0) { 258 errors.add(new Exception("@Parameter(" + index + ") is never used.")); 259 } else if (numberOfUse > 1) { 260 errors.add(new Exception("@Parameter(" + index + ") is used more than once (" + numberOfUse + ").")); 261 } 262 } 263 } 264 } 265 266 @Override 267 protected Statement classBlock(RunNotifier notifier) { 268 return childrenInvoker(notifier); 269 } 270 271 @Override 272 protected Annotation[] getRunnerAnnotations() { 273 return new Annotation[0]; 274 } 275 } 276 277 private static final List<Runner> NO_RUNNERS = Collections.<Runner>emptyList(); 278 279 private final ArrayList<Runner> runners = new ArrayList<Runner>(); 280 281 /** 282 * Only called reflectively. Do not use programmatically. 283 */ 284 public Parameterized(Class<?> klass) throws Throwable { 285 super(klass, NO_RUNNERS); 286 Parameters parameters = getParametersMethod().getAnnotation( 287 Parameters.class); 288 createRunnersForParameters(allParameters(), parameters.name()); 289 } 290 291 @Override 292 protected List<Runner> getChildren() { 293 return runners; 294 } 295 296 protected Runner createRunner(String pattern, int index, Object[] parameters) throws InitializationError { 297 return new TestClassRunnerForParameters(getTestClass().getJavaClass(), pattern, index, parameters); 298 } 299 300 @SuppressWarnings("unchecked") 301 private Iterable<Object[]> allParameters() throws Throwable { 302 Object parameters = getParametersMethod().invokeExplosively(null); 303 if (parameters instanceof Iterable) { 304 return (Iterable<Object[]>) parameters; 305 } else { 306 throw parametersMethodReturnedWrongType(); 307 } 308 } 309 310 private FrameworkMethod getParametersMethod() throws Exception { 311 List<FrameworkMethod> methods = getTestClass().getAnnotatedMethods( 312 Parameters.class); 313 for (FrameworkMethod each : methods) { 314 if (each.isStatic() && each.isPublic()) { 315 return each; 316 } 317 } 318 319 throw new Exception("No public static parameters method on class " 320 + getTestClass().getName()); 321 } 322 323 private void createRunnersForParameters(Iterable<Object[]> allParameters, String namePattern) throws Exception { 324 try { 325 int i = 0; 326 for (Object[] parametersOfSingleTest : allParameters) { 327 runners.add(createRunner(namePattern, i++, parametersOfSingleTest)); 328 } 329 } catch (ClassCastException e) { 330 throw parametersMethodReturnedWrongType(); 331 } 332 } 333 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 343 private List<FrameworkField> getAnnotatedFieldsByParameter() { 344 return getTestClass().getAnnotatedFields(Parameter.class); 345 } 346 347 private boolean fieldsAreAnnotated() { 348 return !getAnnotatedFieldsByParameter().isEmpty(); 349 } 350}