Home | History | Annotate | Download | only in runners
      1 package org.junit.runners;
      2 
      3 import java.lang.annotation.Annotation;
      4 import java.lang.annotation.ElementType;
      5 import java.lang.annotation.Retention;
      6 import java.lang.annotation.RetentionPolicy;
      7 import java.lang.annotation.Target;
      8 import java.lang.reflect.Modifier;
      9 import java.util.ArrayList;
     10 import java.util.Collections;
     11 import java.util.List;
     12 
     13 import org.junit.runner.Runner;
     14 import org.junit.runner.notification.RunNotifier;
     15 import org.junit.runners.model.FrameworkMethod;
     16 import org.junit.runners.model.InitializationError;
     17 import org.junit.runners.model.Statement;
     18 import org.junit.runners.model.TestClass;
     19 
     20 /**
     21  * <p>
     22  * The custom runner <code>Parameterized</code> implements parameterized tests.
     23  * When running a parameterized test class, instances are created for the
     24  * cross-product of the test methods and the test data elements.
     25  * </p>
     26  *
     27  * For example, to test a Fibonacci function, write:
     28  *
     29  * <pre>
     30  * &#064;RunWith(Parameterized.class)
     31  * public class FibonacciTest {
     32  * 	&#064;Parameters
     33  * 	public static List&lt;Object[]&gt; data() {
     34  * 		return Arrays.asList(new Object[][] {
     35  * 			{ 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 }
     36  * 		});
     37  * 	}
     38  *
     39  * 	private int fInput;
     40  *
     41  * 	private int fExpected;
     42  *
     43  * 	public FibonacciTest(int input, int expected) {
     44  * 		fInput= input;
     45  * 		fExpected= expected;
     46  * 	}
     47  *
     48  * 	&#064;Test
     49  * 	public void test() {
     50  * 		assertEquals(fExpected, Fibonacci.compute(fInput));
     51  * 	}
     52  * }
     53  * </pre>
     54  *
     55  * <p>
     56  * Each instance of <code>FibonacciTest</code> will be constructed using the
     57  * two-argument constructor and the data values in the
     58  * <code>&#064;Parameters</code> method.
     59  * </p>
     60  */
     61 public class Parameterized extends Suite {
     62 	/**
     63 	 * Annotation for a method which provides parameters to be injected into the
     64 	 * test class constructor by <code>Parameterized</code>
     65 	 */
     66 	@Retention(RetentionPolicy.RUNTIME)
     67 	@Target(ElementType.METHOD)
     68 	public static @interface Parameters {
     69 	}
     70 
     71 	private class TestClassRunnerForParameters extends
     72 			BlockJUnit4ClassRunner {
     73 		private final int fParameterSetNumber;
     74 
     75 		private final List<Object[]> fParameterList;
     76 
     77 		TestClassRunnerForParameters(Class<?> type,
     78 				List<Object[]> parameterList, int i) throws InitializationError {
     79 			super(type);
     80 			fParameterList= parameterList;
     81 			fParameterSetNumber= i;
     82 		}
     83 
     84 		@Override
     85 		public Object createTest() throws Exception {
     86 			return getTestClass().getOnlyConstructor().newInstance(
     87 					computeParams());
     88 		}
     89 
     90 		private Object[] computeParams() throws Exception {
     91 			try {
     92 				return fParameterList.get(fParameterSetNumber);
     93 			} catch (ClassCastException e) {
     94 				throw new Exception(String.format(
     95 						"%s.%s() must return a Collection of arrays.",
     96 						getTestClass().getName(), getParametersMethod(
     97 								getTestClass()).getName()));
     98 			}
     99 		}
    100 
    101 		@Override
    102 		protected String getName() {
    103 			return String.format("[%s]", fParameterSetNumber);
    104 		}
    105 
    106 		@Override
    107 		protected String testName(final FrameworkMethod method) {
    108 			return String.format("%s[%s]", method.getName(),
    109 					fParameterSetNumber);
    110 		}
    111 
    112 		@Override
    113 		protected void validateConstructor(List<Throwable> errors) {
    114 			validateOnlyOneConstructor(errors);
    115 		}
    116 
    117 		@Override
    118 		protected Statement classBlock(RunNotifier notifier) {
    119 			return childrenInvoker(notifier);
    120 		}
    121 
    122 		@Override
    123 		protected Annotation[] getRunnerAnnotations() {
    124 			return new Annotation[0];
    125 		}
    126 	}
    127 
    128 	private final ArrayList<Runner> runners= new ArrayList<Runner>();
    129 
    130 	/**
    131 	 * Only called reflectively. Do not use programmatically.
    132 	 */
    133 	public Parameterized(Class<?> klass) throws Throwable {
    134 		super(klass, Collections.<Runner>emptyList());
    135 		List<Object[]> parametersList= getParametersList(getTestClass());
    136 		for (int i= 0; i < parametersList.size(); i++)
    137 			runners.add(new TestClassRunnerForParameters(getTestClass().getJavaClass(),
    138 					parametersList, i));
    139 	}
    140 
    141 	@Override
    142 	protected List<Runner> getChildren() {
    143 		return runners;
    144 	}
    145 
    146 	@SuppressWarnings("unchecked")
    147 	private List<Object[]> getParametersList(TestClass klass)
    148 			throws Throwable {
    149 		return (List<Object[]>) getParametersMethod(klass).invokeExplosively(
    150 				null);
    151 	}
    152 
    153 	private FrameworkMethod getParametersMethod(TestClass testClass)
    154 			throws Exception {
    155 		List<FrameworkMethod> methods= testClass
    156 				.getAnnotatedMethods(Parameters.class);
    157 		for (FrameworkMethod each : methods) {
    158 			int modifiers= each.getMethod().getModifiers();
    159 			if (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))
    160 				return each;
    161 		}
    162 
    163 		throw new Exception("No public static parameters method on class "
    164 				+ testClass.getName());
    165 	}
    166 
    167 }
    168