web-dev-qa-db-fra.com

Changer les noms des tests paramétrés

Est-il possible 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] - à quelque chose de significatif.

190
Epaga

Cette fonctionnalité est devenue JUnit 4.11 .

Pour utiliser changer le nom des tests paramétrés, vous dites:

@Parameters(name="namestring")

namestring est une chaîne pouvant contenir les espaces réservés 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ètre
  • etc

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

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

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

281
rescdsk

En regardant JUnit 4.5, son coureur ne le supporte clairement pas, car cette logique est enfouie dans une classe privée à l'intérieur de la classe Parameterized. Vous ne pouvez pas utiliser le coureur paramétré JUnit et créer le vôtre qui comprendrait le concept de noms (ce qui pose la question de savoir comment vous pouvez définir un nom ...).

Du point de vue de JUnit, ce serait bien si, au lieu (ou en plus) de simplement passer un incrément, ils transmettaient 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 à l'adresse www.junit.org.

37
Yishai

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 en utilisant la représentation sous forme de chaîne du premier argument de chaque tableau de paramètres 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 d'utilisation à:

http://code.google.com/p/migen/source/browse/trunk/Java/src/.../ServerBuilderTest.java?r=3789

La description du test se présente joliment 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 bord saignant. :-)

Pour l'utiliser, il vous suffit de copier cette classe (GPL v3) et de modifier @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 les versions ultérieures de JUnit traitent de ce problème, mais même si c'était le cas, je ne peux pas mettre à jour JUnit car tous mes co-développeurs devraient le faire également et nous avons des priorités plus élevées que le réoutillage. Par conséquent, le travail dans la classe doit pouvoir être compilé par plusieurs versions de JUnit.


Remarque: il y a un peu de réflexion jiggery-pokery afin qu'il se retrouve dans les différentes versions de JUnit énumérées ci-dessus. La version spécifique à JUnit 4.3.1 est disponible ici et, pour JUnit 4.4 et 4.5, ici .

20
darrenp

Avec Parameterized en tant que modèle, j’ai écrit mon propre programme d’exécution/suite de test personnalisé - cela ne m'a pris qu’une demi-heure. Il est légèrement différent de LabelledParameterized de darrenp en ce sens qu'il vous permet de spécifier explicitement un nom plutôt que de vous fier au toString() du premier paramètre.

De plus, il n'utilise pas de tableaux parce que 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);
  }

}
13
David Moles

à partir de junit4.8.2, vous pouvez créer votre propre classe MyParameterized en copiant simplement la classe paramétrée. modifiez les méthodes getName () et testName () dans TestClassRunnerForParameters.

6
yliang

Vous pouvez également essayer JUnitParams: http://code.google.com/p/junitparams/

6
dsaff

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.

2
Terra Caines

J'utilise énormément l'importation statique pour Assert et ses amis. Il est donc facile pour moi de redéfinir une 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. Il suffit de le transmettre en tant que premiers éléments de votre tableau de paramètres pour chaque test. Cela aide également à é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}
    });
}
2
binkley

Tout cela ne fonctionnait pas pour moi, alors j'ai obtenu le code source de Parameterized et je l'ai modifié pour créer un nouveau programme d'exécution de test. Je n'ai pas eu à beaucoup changer 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;
}
}
2
Christian

Une solution de contournement consisterait à capturer et à imbriquer tous les objets Throwables dans un nouveau fichier Throwable avec un message personnalisé contenant 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 la pile du test ayant échoué est la suivante:

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
2
mmirwaldt

Puisque le paramètre auquel vous avez accédé (par exemple, avec "{0}" Renvoie toujours la représentation toString(), une solution de contournement consisterait à effectuer une implémentation anonyme et à 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...
    );
}
0
Sina Madani

Découvrez JUnitParams, comme mentionné par dsaff, fonctionne avec ant pour créer des descriptions de méthodes de test paramétrées dans le rapport html.

C'était après avoir essayé LabelledParameterized et constaté que, bien que cela fonctionne avec Eclipse, il ne fonctionnait pas avec ant en ce qui concerne le rapport html.

À votre santé,

0
quarkonium