1
|
1 |
package org.junit.runners;
|
|
2 |
|
|
3 |
import static org.junit.Assert.assertEquals;
|
|
4 |
|
|
5 |
import java.lang.annotation.Annotation;
|
|
6 |
import java.lang.annotation.ElementType;
|
|
7 |
import java.lang.annotation.Retention;
|
|
8 |
import java.lang.annotation.RetentionPolicy;
|
|
9 |
import java.lang.annotation.Target;
|
|
10 |
import java.lang.reflect.Constructor;
|
|
11 |
import java.lang.reflect.InvocationTargetException;
|
|
12 |
import java.lang.reflect.Method;
|
|
13 |
import java.lang.reflect.Modifier;
|
|
14 |
import java.util.ArrayList;
|
|
15 |
import java.util.Collection;
|
|
16 |
import java.util.List;
|
|
17 |
|
|
18 |
import org.junit.internal.runners.CompositeRunner;
|
|
19 |
import org.junit.internal.runners.MethodValidator;
|
|
20 |
import org.junit.internal.runners.TestClassMethodsRunner;
|
|
21 |
import org.junit.internal.runners.TestClassRunner;
|
|
22 |
|
|
23 |
/** <p>The custom runner <code>Parameterized</code> implements parameterized
|
|
24 |
* tests. When running a parameterized test class, instances are created for the
|
|
25 |
* cross-product of the test methods and the test data elements.</p>
|
|
26 |
*
|
|
27 |
* For example, to test a Fibonacci function, write:
|
|
28 |
* <pre>
|
|
29 |
* @RunWith(Parameterized.class)
|
|
30 |
* public class FibonacciTest {
|
|
31 |
* @Parameters
|
|
32 |
* public static Collection<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 |
* private int fExpected;
|
|
39 |
*
|
|
40 |
* public FibonacciTest(int input, int expected) {
|
|
41 |
* fInput= input;
|
|
42 |
* fExpected= expected;
|
|
43 |
* }
|
|
44 |
*
|
|
45 |
* @Test public void test() {
|
|
46 |
* assertEquals(fExpected, Fibonacci.compute(fInput));
|
|
47 |
* }
|
|
48 |
* }
|
|
49 |
* </pre>
|
|
50 |
*
|
|
51 |
* <p>Each instance of <code>FibonacciTest</code> will be constructed using the two-argument
|
|
52 |
* constructor and the data values in the <code>@Parameters</code> method.</p>
|
|
53 |
*/
|
|
54 |
public class Parameterized extends TestClassRunner {
|
|
55 |
@Retention(RetentionPolicy.RUNTIME)
|
|
56 |
@Target(ElementType.METHOD)
|
|
57 |
public static @interface Parameters {
|
|
58 |
}
|
|
59 |
|
|
60 |
public static Collection<Object[]> eachOne(Object... params) {
|
|
61 |
List<Object[]> results= new ArrayList<Object[]>();
|
|
62 |
for (Object param : params)
|
|
63 |
results.add(new Object[] { param });
|
|
64 |
return results;
|
|
65 |
}
|
|
66 |
|
|
67 |
// TODO: single-class this extension
|
|
68 |
|
|
69 |
private static class TestClassRunnerForParameters extends TestClassMethodsRunner {
|
|
70 |
private final Object[] fParameters;
|
|
71 |
|
|
72 |
private final int fParameterSetNumber;
|
|
73 |
|
|
74 |
private final Constructor<?> fConstructor;
|
|
75 |
|
|
76 |
private TestClassRunnerForParameters(Class<?> klass, Object[] parameters, int i) {
|
|
77 |
super(klass);
|
|
78 |
fParameters= parameters;
|
|
79 |
fParameterSetNumber= i;
|
|
80 |
fConstructor= getOnlyConstructor();
|
|
81 |
}
|
|
82 |
|
|
83 |
@Override
|
|
84 |
protected Object createTest() throws Exception {
|
|
85 |
return fConstructor.newInstance(fParameters);
|
|
86 |
}
|
|
87 |
|
|
88 |
@Override
|
|
89 |
protected String getName() {
|
|
90 |
return String.format("[%s]", fParameterSetNumber);
|
|
91 |
}
|
|
92 |
|
|
93 |
@Override
|
|
94 |
protected String testName(final Method method) {
|
|
95 |
return String.format("%s[%s]", method.getName(), fParameterSetNumber);
|
|
96 |
}
|
|
97 |
|
|
98 |
private Constructor<?> getOnlyConstructor() {
|
|
99 |
Constructor<?>[] constructors= getTestClass().getConstructors();
|
|
100 |
assertEquals(1, constructors.length);
|
|
101 |
return constructors[0];
|
|
102 |
}
|
|
103 |
}
|
|
104 |
|
|
105 |
// TODO: I think this now eagerly reads parameters, which was never the point.
|
|
106 |
|
|
107 |
public static class RunAllParameterMethods extends CompositeRunner {
|
|
108 |
private final Class<?> fKlass;
|
|
109 |
|
|
110 |
public RunAllParameterMethods(Class<?> klass) throws Exception {
|
|
111 |
super(klass.getName());
|
|
112 |
fKlass= klass;
|
|
113 |
int i= 0;
|
|
114 |
for (final Object each : getParametersList()) {
|
|
115 |
if (each instanceof Object[])
|
|
116 |
super.add(new TestClassRunnerForParameters(klass, (Object[])each, i++));
|
|
117 |
else
|
|
118 |
throw new Exception(String.format("%s.%s() must return a Collection of arrays.", fKlass.getName(), getParametersMethod().getName()));
|
|
119 |
}
|
|
120 |
}
|
|
121 |
|
|
122 |
private Collection<?> getParametersList() throws IllegalAccessException, InvocationTargetException, Exception {
|
|
123 |
return (Collection<?>) getParametersMethod().invoke(null);
|
|
124 |
}
|
|
125 |
|
|
126 |
private Method getParametersMethod() throws Exception {
|
|
127 |
for (Method each : fKlass.getMethods()) {
|
|
128 |
if (Modifier.isStatic(each.getModifiers())) {
|
|
129 |
Annotation[] annotations= each.getAnnotations();
|
|
130 |
for (Annotation annotation : annotations) {
|
|
131 |
if (annotation.annotationType() == Parameters.class)
|
|
132 |
return each;
|
|
133 |
}
|
|
134 |
}
|
|
135 |
}
|
|
136 |
throw new Exception("No public static parameters method on class "
|
|
137 |
+ getName());
|
|
138 |
}
|
|
139 |
}
|
|
140 |
|
|
141 |
public Parameterized(final Class<?> klass) throws Exception {
|
|
142 |
super(klass, new RunAllParameterMethods(klass));
|
|
143 |
}
|
|
144 |
|
|
145 |
@Override
|
|
146 |
protected void validate(MethodValidator methodValidator) {
|
|
147 |
methodValidator.validateStaticMethods();
|
|
148 |
methodValidator.validateInstanceMethods();
|
|
149 |
}
|
|
150 |
}
|