Réponses:
Cette fonctionnalité a intégré JUnit 4.11 .
Pour utiliser changer le nom des tests paramétrés, vous dites:
@Parameters(name="namestring")
namestring
est une chaîne qui peut avoir les espaces réservés spéciaux suivants:
{index}
- l'index de cet ensemble d'arguments. La valeur par défaut namestring
est {index}
.{0}
- la première valeur de paramètre de cette invocation du test.{1}
- la deuxième valeur du paramètreLe nom final du test sera le nom de la méthode de test, suivi des namestring
parenthèses, comme indiqué ci-dessous.
Par exemple (adapté du test unitaire pour l' Parameterized
annotation):
@RunWith(Parameterized.class)
static public class FibonacciTest {
@Parameters( name = "{index}: fib({0})={1}" )
public static Iterable<Object[]> data() {
return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 },
{ 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });
}
private final int fInput;
private final int fExpected;
public FibonacciTest(int input, int expected) {
fInput= input;
fExpected= expected;
}
@Test
public void testFib() {
assertEquals(fExpected, fib(fInput));
}
private int fib(int x) {
// TODO: actually calculate Fibonacci numbers
return 0;
}
}
donnera des noms comme testFib[1: fib(1)=1]
et testFib[4: fib(4)=3]
. (La testFib
partie du nom est le nom de la méthode du @Test
).
{0}
et {1}
sont des tableaux? JUnit devrait idéalement appeler Arrays.toString({0})
, non {0}.toString()
. Par exemple, ma data()
méthode revient Arrays.asList(new Object[][] {{ new int[] { 1, 3, 2 }, new int[] { 1, 2, 3 } }});
.
En regardant JUnit 4.5, son exécuteur ne prend clairement pas en charge cela, car cette logique est enterrée dans une classe privée à l'intérieur de la classe Parameterized. Vous ne pouviez pas utiliser le runner paramétré JUnit et créer le vôtre à la place qui comprendrait le concept de noms (ce qui conduit à la question de savoir comment définir un nom ...).
Du point de vue de JUnit, ce serait bien si au lieu de (ou en plus de) simplement passer un incrément, ils passeraient les arguments délimités par des virgules. TestNG fait cela. Si la fonctionnalité est importante pour vous, vous pouvez commenter la liste de diffusion yahoo référencée sur www.junit.org.
J'ai récemment rencontré le même problème lors de l'utilisation de JUnit 4.3.1. J'ai implémenté une nouvelle classe qui étend Parameterized appelée LabelledParameterized. Il a été testé avec JUnit 4.3.1, 4.4 et 4.5. Il reconstruit l'instance Description à l'aide de la représentation String du premier argument de chaque tableau de paramètres à partir de la méthode @Parameters. Vous pouvez voir le code pour cela à:
http://code.google.com/p/migen/source/browse/trunk/java/src/.../LabelledParameterized.java?r=3789
et un exemple de son utilisation à:
http://code.google.com/p/migen/source/browse/trunk/java/src/.../ServerBuilderTest.java?r=3789
La description du test se formate bien dans Eclipse, ce que je voulais car cela rend les tests échoués beaucoup plus faciles à trouver! Je vais probablement affiner et documenter les cours au cours des prochains jours / semaines. Jeter le '?' une partie des URL si vous voulez le dernier cri. :-)
Pour l'utiliser, il vous suffit de copier cette classe (GPL v3) et de changer @RunWith (Parameterized.class) en @RunWith (LabelledParameterized.class) en supposant que le premier élément de votre liste de paramètres est une étiquette sensible.
Je ne sais pas si des versions ultérieures de JUnit résolvent ce problème, mais même si c'est le cas, je ne peux pas mettre à jour JUnit car tous mes co-développeurs devraient également mettre à jour et nous avons des priorités plus élevées que le ré-outillage. D'où le travail dans la classe pour être compilable par plusieurs versions de JUnit.
Remarque: il y a une réflexion jiggery-pokery afin qu'elle s'exécute sur les différentes versions de JUnit comme indiqué ci-dessus. La version spécifique à JUnit 4.3.1 peut être trouvée ici et, pour JUnit 4.4 et 4.5, ici .
execute[0], execute[1] ... execute[n]
dans les rapports de test générés.
Avec Parameterized
comme modèle, j'ai écrit mon propre runner / suite de test personnalisé - cela n'a pris qu'une demi-heure environ. Il est légèrement différent de celui de darrenp LabelledParameterized
en ce qu'il vous permet de spécifier un nom explicitement plutôt que de vous fier aux premiers paramètres toString()
.
Il n'utilise pas non plus de tableaux car je déteste les tableaux. :)
public class PolySuite extends Suite {
// //////////////////////////////
// Public helper interfaces
/**
* Annotation for a method which returns a {@link Configuration}
* to be injected into the test class constructor
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public static @interface Config {
}
public static interface Configuration {
int size();
Object getTestValue(int index);
String getTestName(int index);
}
// //////////////////////////////
// Fields
private final List<Runner> runners;
// //////////////////////////////
// Constructor
/**
* Only called reflectively. Do not use programmatically.
* @param c the test class
* @throws Throwable if something bad happens
*/
public PolySuite(Class<?> c) throws Throwable {
super(c, Collections.<Runner>emptyList());
TestClass testClass = getTestClass();
Class<?> jTestClass = testClass.getJavaClass();
Configuration configuration = getConfiguration(testClass);
List<Runner> runners = new ArrayList<Runner>();
for (int i = 0, size = configuration.size(); i < size; i++) {
SingleRunner runner = new SingleRunner(jTestClass, configuration.getTestValue(i), configuration.getTestName(i));
runners.add(runner);
}
this.runners = runners;
}
// //////////////////////////////
// Overrides
@Override
protected List<Runner> getChildren() {
return runners;
}
// //////////////////////////////
// Private
private Configuration getConfiguration(TestClass testClass) throws Throwable {
return (Configuration) getConfigMethod(testClass).invokeExplosively(null);
}
private FrameworkMethod getConfigMethod(TestClass testClass) {
List<FrameworkMethod> methods = testClass.getAnnotatedMethods(Config.class);
if (methods.isEmpty()) {
throw new IllegalStateException("@" + Config.class.getSimpleName() + " method not found");
}
if (methods.size() > 1) {
throw new IllegalStateException("Too many @" + Config.class.getSimpleName() + " methods");
}
FrameworkMethod method = methods.get(0);
int modifiers = method.getMethod().getModifiers();
if (!(Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {
throw new IllegalStateException("@" + Config.class.getSimpleName() + " method \"" + method.getName() + "\" must be public static");
}
return method;
}
// //////////////////////////////
// Helper classes
private static class SingleRunner extends BlockJUnit4ClassRunner {
private final Object testVal;
private final String testName;
SingleRunner(Class<?> testClass, Object testVal, String testName) throws InitializationError {
super(testClass);
this.testVal = testVal;
this.testName = testName;
}
@Override
protected Object createTest() throws Exception {
return getTestClass().getOnlyConstructor().newInstance(testVal);
}
@Override
protected String getName() {
return testName;
}
@Override
protected String testName(FrameworkMethod method) {
return testName + ": " + method.getName();
}
@Override
protected void validateConstructor(List<Throwable> errors) {
validateOnlyOneConstructor(errors);
}
@Override
protected Statement classBlock(RunNotifier notifier) {
return childrenInvoker(notifier);
}
}
}
Et un exemple:
@RunWith(PolySuite.class)
public class PolySuiteExample {
// //////////////////////////////
// Fixture
@Config
public static Configuration getConfig() {
return new Configuration() {
@Override
public int size() {
return 10;
}
@Override
public Integer getTestValue(int index) {
return index * 2;
}
@Override
public String getTestName(int index) {
return "test" + index;
}
};
}
// //////////////////////////////
// Fields
private final int testVal;
// //////////////////////////////
// Constructor
public PolySuiteExample(int testVal) {
this.testVal = testVal;
}
// //////////////////////////////
// Test
@Ignore
@Test
public void odd() {
assertFalse(testVal % 2 == 0);
}
@Test
public void even() {
assertTrue(testVal % 2 == 0);
}
}
à partir de junit4.8.2, vous pouvez créer votre propre classe MyParameterized en copiant simplement la classe Parameterized. modifiez les méthodes getName () et testName () dans TestClassRunnerForParameters.
Vous pouvez également essayer JUnitParams: http://code.google.com/p/junitparams/
Vous pouvez créer une méthode comme
@Test
public void name() {
Assert.assertEquals("", inboundFileName);
}
Bien que je ne l'utilise pas tout le temps, il serait utile de savoir exactement quel est le numéro de test 143.
J'utilise largement l'importation statique pour Assert et ses amis, il est donc facile pour moi de redéfinir l'assertion:
private <T> void assertThat(final T actual, final Matcher<T> expected) {
Assert.assertThat(editThisToDisplaySomethingForYourDatum, actual, expected);
}
Par exemple, vous pouvez ajouter un champ "nom" à votre classe de test, initialisé dans le constructeur, et l'afficher en cas d'échec du test. Passez-le simplement comme premier élément de votre tableau de paramètres pour chaque test. Cela permet également d'étiqueter les données:
public ExampleTest(final String testLabel, final int one, final int two) {
this.testLabel = testLabel;
// ...
}
@Parameters
public static Collection<Object[]> data() {
return asList(new Object[][]{
{"first test", 3, 4},
{"second test", 5, 6}
});
}
Rien de tout cela ne fonctionnait pour moi, j'ai donc obtenu la source de Parameterized et je l'ai modifiée pour créer un nouveau testeur. Je n'ai pas eu à changer grand chose mais ça marche !!!
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.junit.Assert;
import org.junit.internal.runners.ClassRoadie;
import org.junit.internal.runners.CompositeRunner;
import org.junit.internal.runners.InitializationError;
import org.junit.internal.runners.JUnit4ClassRunner;
import org.junit.internal.runners.MethodValidator;
import org.junit.internal.runners.TestClass;
import org.junit.runner.notification.RunNotifier;
public class LabelledParameterized extends CompositeRunner {
static class TestClassRunnerForParameters extends JUnit4ClassRunner {
private final Object[] fParameters;
private final String fParameterFirstValue;
private final Constructor<?> fConstructor;
TestClassRunnerForParameters(TestClass testClass, Object[] parameters, int i) throws InitializationError {
super(testClass.getJavaClass()); // todo
fParameters = parameters;
if (parameters != null) {
fParameterFirstValue = Arrays.asList(parameters).toString();
} else {
fParameterFirstValue = String.valueOf(i);
}
fConstructor = getOnlyConstructor();
}
@Override
protected Object createTest() throws Exception {
return fConstructor.newInstance(fParameters);
}
@Override
protected String getName() {
return String.format("%s", fParameterFirstValue);
}
@Override
protected String testName(final Method method) {
return String.format("%s%s", method.getName(), fParameterFirstValue);
}
private Constructor<?> getOnlyConstructor() {
Constructor<?>[] constructors = getTestClass().getJavaClass().getConstructors();
Assert.assertEquals(1, constructors.length);
return constructors[0];
}
@Override
protected void validate() throws InitializationError {
// do nothing: validated before.
}
@Override
public void run(RunNotifier notifier) {
runMethods(notifier);
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public static @interface Parameters {
}
private final TestClass fTestClass;
public LabelledParameterized(Class<?> klass) throws Exception {
super(klass.getName());
fTestClass = new TestClass(klass);
MethodValidator methodValidator = new MethodValidator(fTestClass);
methodValidator.validateStaticMethods();
methodValidator.validateInstanceMethods();
methodValidator.assertValid();
int i = 0;
for (final Object each : getParametersList()) {
if (each instanceof Object[])
add(new TestClassRunnerForParameters(fTestClass, (Object[]) each, i++));
else
throw new Exception(String.format("%s.%s() must return a Collection of arrays.", fTestClass.getName(), getParametersMethod().getName()));
}
}
@Override
public void run(final RunNotifier notifier) {
new ClassRoadie(notifier, fTestClass, getDescription(), new Runnable() {
public void run() {
runChildren(notifier);
}
}).runProtected();
}
private Collection<?> getParametersList() throws IllegalAccessException, InvocationTargetException, Exception {
return (Collection<?>) getParametersMethod().invoke(null);
}
private Method getParametersMethod() throws Exception {
List<Method> methods = fTestClass.getAnnotatedMethods(Parameters.class);
for (Method each : methods) {
int modifiers = each.getModifiers();
if (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))
return each;
}
throw new Exception("No public static parameters method on class " + getName());
}
public static Collection<Object[]> eachOne(Object... params) {
List<Object[]> results = new ArrayList<Object[]>();
for (Object param : params)
results.add(new Object[] { param });
return results;
}
}
Une solution de contournement serait d'attraper et d'imbriquer tous les Throwables dans un nouveau Throwable avec un message personnalisé qui contient toutes les informations sur les paramètres. Le message apparaîtrait dans la trace de la pile. Cela fonctionne chaque fois qu'un test échoue pour toutes les assertions, erreurs et exceptions car ce sont toutes des sous-classes de Throwable.
Mon code ressemble à ceci:
@RunWith(Parameterized.class)
public class ParameterizedTest {
int parameter;
public ParameterizedTest(int parameter) {
super();
this.parameter = parameter;
}
@Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] { {1}, {2} });
}
@Test
public void test() throws Throwable {
try {
assertTrue(parameter%2==0);
}
catch(Throwable thrown) {
throw new Throwable("parameter="+parameter, thrown);
}
}
}
La trace de pile du test ayant échoué est:
java.lang.Throwable: parameter=1
at sample.ParameterizedTest.test(ParameterizedTest.java:34)
Caused by: java.lang.AssertionError
at org.junit.Assert.fail(Assert.java:92)
at org.junit.Assert.assertTrue(Assert.java:43)
at org.junit.Assert.assertTrue(Assert.java:54)
at sample.ParameterizedTest.test(ParameterizedTest.java:31)
... 31 more
Découvrez JUnitParams comme dsaff l'a mentionné, fonctionne en utilisant ant pour construire des descriptions de méthodes de test paramétrées dans le rapport html.
C'était après avoir essayé LabelledParameterized et découvert que bien qu'il fonctionne avec eclipse, il ne fonctionne pas avec ant en ce qui concerne le rapport html.
À votre santé,
Puisque le paramètre accédé (par exemple avec "{0}"
toujours renvoie la toString()
représentation, une solution de contournement serait de faire une implémentation anonyme et de remplacer toString()
dans chaque cas. Par exemple:
public static Iterable<? extends Object> data() {
return Arrays.asList(
new MyObject(myParams...) {public String toString(){return "my custom test name";}},
new MyObject(myParams...) {public String toString(){return "my other custom test name";}},
//etc...
);
}