Skip to content

Commit

Permalink
Merge pull request junit-team#484 from jherault/feature
Browse files Browse the repository at this point in the history
[Enhancement] for parameterized JUnit test, Parameter annotation - V3
  • Loading branch information
David Saff committed Aug 21, 2012
2 parents f9ec047 + def7dc5 commit 2e53904
Show file tree
Hide file tree
Showing 3 changed files with 242 additions and 3 deletions.
4 changes: 2 additions & 2 deletions src/main/java/org/junit/runners/BlockJUnit4ClassRunner.java
Expand Up @@ -169,7 +169,7 @@ protected void validateInstanceMethods(List<Throwable> errors) {
errors.add(new Exception("No runnable methods"));
}

private void validateFields(List<Throwable> errors) {
protected void validateFields(List<Throwable> errors) {
RULE_VALIDATOR.validate(getTestClass(), errors);
}

Expand Down Expand Up @@ -417,4 +417,4 @@ private long getTimeout(Test annotation) {
return 0;
return annotation.timeout();
}
}
}
121 changes: 121 additions & 0 deletions src/main/java/org/junit/runners/Parameterized.java
Expand Up @@ -5,13 +5,15 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.junit.runner.Runner;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.model.FrameworkField;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;
Expand Down Expand Up @@ -73,6 +75,35 @@
* names like <code>[1: fib(3)=2]</code>. If you don't use the name parameter,
* then the current parameter index is used as name.
* </p>
*
* You can also write:
*
* <pre>
* &#064;RunWith(Parameterized.class)
* public class FibonacciTest {
* &#064;Parameters
* public static Iterable&lt;Object[]&gt; data() {
* return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 },
* { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });
* }
* &#064;Parameter(0)
* public int fInput;
*
* &#064;Parameter(1)
* public int fExpected;
*
* &#064;Test
* public void test() {
* assertEquals(fExpected, Fibonacci.compute(fInput));
* }
* }
* </pre>
*
* <p>
* Each instance of <code>FibonacciTest</code> will be constructed with the default constructor
* and fields annotated by <code>&#064;Parameter</code> will be initialized
* with the data values in the <code>&#064;Parameters</code> method.
* </p>
* @since 4.0
*/
public class Parameterized extends Suite {
Expand Down Expand Up @@ -108,6 +139,26 @@ public class Parameterized extends Suite {
String name() default "{index}";
}

/**
* Annotation for fields of the test class which will be initialized by the
* method annotated by <code>Parameters</code><br/>
* By using directly this annotation, the test class constructor isn't needed.<br/>
* Index range must start at 0.
* Default value is 0.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public static @interface Parameter {
/**
* Method that returns the index of the parameter in the array
* returned by the method annotated by <code>Parameters</code>.<br/>
* Index range must start at 0.
* Default value is 0.
* @return the index of the parameter.
*/
int value() default 0;
}

private class TestClassRunnerForParameters extends BlockJUnit4ClassRunner {
private final Object[] fParameters;

Expand All @@ -122,8 +173,38 @@ private class TestClassRunnerForParameters extends BlockJUnit4ClassRunner {

@Override
public Object createTest() throws Exception {
if (fieldsAreAnnotated()) {
return createTestUsingFieldInjection();
} else {
return createTestUsingConstructorInjection();
}
}

private Object createTestUsingConstructorInjection() throws Exception {
return getTestClass().getOnlyConstructor().newInstance(fParameters);
}

private Object createTestUsingFieldInjection() throws Exception {
List<FrameworkField> annotatedFieldsByParameter = getAnnotatedFieldsByParameter();
if (annotatedFieldsByParameter.size() != fParameters.length)
throw new Exception("Wrong number of parameters and @Parameter fields."+
" @Parameter fields counted: "+annotatedFieldsByParameter.size()+", available parameters: "+fParameters.length+".");
Object testClassInstance = getTestClass().getJavaClass().newInstance();
for (FrameworkField each : annotatedFieldsByParameter) {
Field field = each.getField();
Parameter annotation = field.getAnnotation(Parameter.class);
int index = annotation.value();
try {
field.set(testClassInstance, fParameters[index]);
} catch(IllegalArgumentException iare) {
throw new Exception(getTestClass().getName() + ": Trying to set "+field.getName()+
" with the value "+fParameters[index]+
" that is not the right type ("+fParameters[index].getClass().getSimpleName()+" instead of "+
field.getType().getSimpleName()+").", iare);
}
}
return testClassInstance;
}

@Override
protected String getName() {
Expand All @@ -138,6 +219,38 @@ protected String testName(FrameworkMethod method) {
@Override
protected void validateConstructor(List<Throwable> errors) {
validateOnlyOneConstructor(errors);
if (fieldsAreAnnotated()) {
validateZeroArgConstructor(errors);
}
}

@Override
protected void validateFields(List<Throwable> errors) {
super.validateFields(errors);
if (fieldsAreAnnotated()) {
List<FrameworkField> annotatedFieldsByParameter = getAnnotatedFieldsByParameter();
int[] usedIndices = new int[annotatedFieldsByParameter.size()];
for (FrameworkField each : annotatedFieldsByParameter) {
int index = each.getField().getAnnotation(Parameter.class).value();
if (index < 0 || index > annotatedFieldsByParameter.size()-1) {
errors.add(
new Exception("Invalid @Parameter value: "+index+". @Parameter fields counted: "+
annotatedFieldsByParameter.size()+". Please use an index between 0 and "+
(annotatedFieldsByParameter.size()-1)+".")
);
} else {
usedIndices[index]++;
}
}
for (int index = 0 ; index < usedIndices.length ; index++) {
int numberOfUse = usedIndices[index];
if (numberOfUse == 0) {
errors.add(new Exception("@Parameter("+index+") is never used."));
} else if (numberOfUse > 1) {
errors.add(new Exception("@Parameter("+index+") is used more than once ("+numberOfUse+")."));
}
}
}
}

@Override
Expand Down Expand Up @@ -224,4 +337,12 @@ private Exception parametersMethodReturnedWrongType() throws Exception {
className, methodName);
return new Exception(message);
}

private List<FrameworkField> getAnnotatedFieldsByParameter() {
return getTestClass().getAnnotatedFields(Parameter.class);
}

private boolean fieldsAreAnnotated() {
return !getAnnotatedFieldsByParameter().isEmpty();
}
}
Expand Up @@ -3,11 +3,13 @@
import static org.hamcrest.CoreMatchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.experimental.results.PrintableResult.testResult;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.junit.AfterClass;
import org.junit.BeforeClass;
Expand All @@ -18,7 +20,9 @@
import org.junit.runner.Result;
import org.junit.runner.RunWith;
import org.junit.runner.Runner;
import org.junit.runner.notification.Failure;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
import org.junit.runners.model.InitializationError;

Expand Down Expand Up @@ -102,6 +106,120 @@ public void usesIndexAsTestName() {
assertEquals("[1]", description.getChildren().get(1).getDisplayName());
}

@RunWith(Parameterized.class)
static public class FibonacciWithParameterizedFieldTest {
@Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 },
{ 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });
}

@Parameter(0)
public int fInput;

@Parameter(1)
public int fExpected;

@Test
public void test() {
assertEquals(fExpected, fib(fInput));
}

private int fib(int x) {
return 0;
}
}

@Test
public void countWithParameterizedField() {
Result result= JUnitCore.runClasses(FibonacciWithParameterizedFieldTest.class);
assertEquals(7, result.getRunCount());
assertEquals(6, result.getFailureCount());
}

@Test
public void failuresNamedCorrectlyWithParameterizedField() {
Result result= JUnitCore.runClasses(FibonacciWithParameterizedFieldTest.class);
assertEquals(String
.format("test[1](%s)", FibonacciWithParameterizedFieldTest.class.getName()), result
.getFailures().get(0).getTestHeader());
}

@Test
public void countBeforeRunWithParameterizedField() throws Exception {
Runner runner= Request.aClass(FibonacciWithParameterizedFieldTest.class).getRunner();
assertEquals(7, runner.testCount());
}

@Test
public void plansNamedCorrectlyWithParameterizedField() throws Exception {
Runner runner= Request.aClass(FibonacciWithParameterizedFieldTest.class).getRunner();
Description description= runner.getDescription();
assertEquals("[0]", description.getChildren().get(0).getDisplayName());
}

@RunWith(Parameterized.class)
static public class BadIndexForAnnotatedFieldTest {
@Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] { { 0 } });
}

@Parameter(2)
public int fInput;

public int fExpected;

@Test
public void test() {
assertEquals(fExpected, fib(fInput));
}

private int fib(int x) {
return 0;
}
}

@Test
public void failureOnInitialization() {
Result result = JUnitCore.runClasses(BadIndexForAnnotatedFieldTest.class);
assertEquals(2, result.getFailureCount());
List<Failure> failures = result.getFailures();
assertEquals("Invalid @Parameter value: 2. @Parameter fields counted: 1. Please use an index between 0 and 0.",
failures.get(0).getException().getMessage());
assertEquals("@Parameter(0) is never used.", failures.get(1).getException().getMessage());
}

@RunWith(Parameterized.class)
static public class BadNumberOfAnnotatedFieldTest {
@Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] { { 0, 0 } });
}

@Parameter(0)
public int fInput;

public int fExpected;

@Test
public void test() {
assertEquals(fExpected, fib(fInput));
}

private int fib(int x) {
return 0;
}
}

@Test
public void numberOfFieldsAndParametersShouldMatch() {
Result result = JUnitCore.runClasses(BadNumberOfAnnotatedFieldTest.class);
assertEquals(1, result.getFailureCount());
List<Failure> failures = result.getFailures();
assertTrue(failures.get(0).getException().getMessage().contains("Wrong number of parameters and @Parameter fields. @Parameter fields counted: 1, available parameters: 2."));
}

private static String fLog;

@RunWith(Parameterized.class)
Expand Down Expand Up @@ -254,4 +372,4 @@ public void aTest() {
public void exceptionWhenPrivateConstructor() throws Throwable {
new Parameterized(PrivateConstructor.class);
}
}
}

0 comments on commit 2e53904

Please sign in to comment.