Modification des noms des tests paramétrés

204

Existe-t-il un moyen de définir mes propres noms de cas de test personnalisés lors de l'utilisation de tests paramétrés dans JUnit4?

Je voudrais changer la valeur par défaut - [Test class].runTest[n]- en quelque chose de significatif.

Epaga
la source

Réponses:

300

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 namestringest {index}.
  • {0} - la première valeur de paramètre de cette invocation du test.
  • {1} - la deuxième valeur du paramètre
  • etc

Le nom final du test sera le nom de la méthode de test, suivi des namestringparenthèses, comme indiqué ci-dessous.

Par exemple (adapté du test unitaire pour l' Parameterizedannotation):

@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 testFibpartie du nom est le nom de la méthode du @Test).

rescdsk
la source
4
Il n'y a aucune raison que ce ne soit pas en 4.11, c'est en master. Maintenant, quand 4.11 sera disponible, c'est une bonne question :-)
Matthew Farwell
1
4.11 est maintenant en version bêta et peut être téléchargé à partir du même lien que ci-dessus :-)
rescdsk
2
Oui, mais il y a un bug. Si vous mettez une parenthèse dans la valeur du paramètre "nom" comme vous le faites dans cette publication, cela interrompt l'affichage du nom du test unitaire dans Eclipse.
djangofan
7
génial, mais si {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 } }});.
dogbane
1
@djangofan Il s'agit d'un bogue Eclipse de 8 ans: bugs.eclipse.org/bugs/show_bug.cgi?id=102512
Pool du
37

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.

Yishai
la source
3
J'apprécierais grandement s'il y avait une amélioration pour cela dans JUnit!
guerda
17
Juste vérifié, il y a une demande de fonctionnalité en suspens à: github.com/KentBeck/junit/issues#issue/44 Veuillez voter.
reccles
8
@Frank, je pense que la version qui résout ce problème n'est pas encore publiée. Ce sera dans JUnit 4.11. À ce moment-là (en supposant que la conception reste la même), il s'agira d'une manière textuelle de spécifier comment vous nommez le test, y compris en prenant des paramètres comme noms. Plutôt sympa, en fait.
Yishai
5
JUnit 4.11 est maintenant disponible :-)
rescdsk
7
Voici le lien mis à jour vers le numéro d'origine github.com/junit-team/junit/issues/44 pour référence future
kldavis4
20

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 .

darrenp
la source
:-) Un de mes co-développeurs a eu un problème aujourd'hui car la version que je pointe dans le message ci-dessus utilise JUnit 4.3.1 (pas 4.4 comme je l'ai dit à l'origine). Il utilise JUnit 4.5.0 et cela a causé des problèmes. Je vais en parler aujourd'hui.
darrenp
J'ai pris un certain temps pour comprendre que vous devez passer le nom du test dans le constructeur, mais pas le mémoriser . Merci pour le code!
giraff
Fonctionne très bien tant que je lance les tests depuis Eclipse. Est-ce que quelqu'un a de l'expérience pour le faire fonctionner avec la tâche Ant de JUnit? Les rapports de test sont nommés execute[0], execute[1] ... execute[n]dans les rapports de test générés.
Henrik Aasted Sørensen
Très agréable. Fonctionne comme un charme. Ce serait bien, si vous pouviez ajouter les informations, qu'il est nécessaire d'ajouter "String label, ..." comme premier paramètre à la méthode @ Test invoquée.
gia
13

Avec Parameterizedcomme 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 LabelledParameterizeden 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);
  }

}
David Moles
la source
6

à 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.

yliang
la source
J'ai essayé mais ça n'aide pas. Lors de la création d'une nouvelle classe, getParametersMethod échoue.
java_enthu
2

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.


la source
2

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}
    });
}
binkley
la source
C'est très bien si le test échoue à une assertion, mais il y a d'autres cas, comme si une exception est levée qui échoue au test, ou si le test s'attend à ce qu'une exception soit levée, qui fait penser au surcoût du nom qui devrait être géré par le cadre.
Yishai
2

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;
}
}
Christian
la source
2

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
mmirwaldt
la source
0

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é,

quarkonium
la source
0

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...
    );
}
Sina Madani
la source