Home | History | Annotate | Download | only in runners
      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  * &#064;RunWith(Parameterized.class)
     30  * public class FibonacciTest {
     31  *     &#064;Parameters(name= &quot;{index}: fib[{0}]={1}&quot;)
     32  *     public static Iterable&lt;Object[]&gt; 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  *     &#064;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>&#064;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>&#064;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  * &#064;RunWith(Parameterized.class)
     78  * public class FibonacciTest {
     79  *  &#064;Parameters
     80  *  public static Iterable&lt;Object[]&gt; 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  *  &#064;Parameter(0)
     86  *  public int fInput;
     87  *
     88  *  &#064;Parameter(1)
     89  *  public int fExpected;
     90  *
     91  *  &#064;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>&#064;Parameter</code>  will be initialized
    100  * with the data values in the <code>&#064;Parameters</code> method.
    101  *
    102  * <p>
    103  * The parameters can be provided as an array, too:
    104  *
    105  * <pre>
    106  * &#064;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  * &#064;Parameters
    120  * public static Iterable&lt;? extends Object&gt; data() {
    121  * 	return Arrays.asList(&quot;first test&quot;, &quot;second test&quot;);
    122  * }
    123  * </pre>
    124  * <p>
    125  * or
    126  * <pre>
    127  * &#064;Parameters
    128  * public static Object[] data() {
    129  * 	return new Object[] { &quot;first test&quot;, &quot;second test&quot; };
    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  * &#064;RunWith(Parameterized.class)
    157  * &#064;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          */
    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          */
    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          */
    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      */
    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 
    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
    266     protected List<Runner> getChildren() {
    267         return runners;
    268     }
    269 
    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")
    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 
    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 
    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 
    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 
    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 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