Namen parametrisierter Tests ändern

203

Gibt es eine Möglichkeit, meine eigenen benutzerdefinierten Testfallnamen festzulegen, wenn parametrisierte Tests in JUnit4 verwendet werden?

Ich möchte die Standardeinstellung - [Test class].runTest[n]- in etwas Sinnvolles ändern .

Epaga
quelle

Antworten:

298

Diese Funktion hat es in JUnit 4.11 geschafft .

Um den Namen parametrisierter Tests zu ändern, sagen Sie:

@Parameters(name="namestring")

namestring ist eine Zeichenfolge, die die folgenden speziellen Platzhalter haben kann:

  • {index}- der Index dieser Argumente. Der Standardwert namestringist {index}.
  • {0} - der erste Parameterwert aus diesem Aufruf des Tests.
  • {1} - der zweite Parameterwert
  • und so weiter

Der endgültige Name des Tests ist der Name der Testmethode, gefolgt von den namestringin Klammern gesetzten Klammern (siehe unten).

Zum Beispiel (angepasst aus dem Unit-Test für die 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;
    }
}

wird Namen wie testFib[1: fib(1)=1]und geben testFib[4: fib(4)=3]. (Der testFibTeil des Namens ist der Methodenname von @Test).

residdsk
quelle
4
Es gibt keinen Grund, warum es nicht in 4.11 wäre, es ist in Master. Nun, wenn 4.11 verfügbar sein wird, ist das eine gute Frage :-)
Matthew Farwell
1
4.11 ist jetzt in der Beta und kann von dem gleichen Link wie oben heruntergeladen werden :-)
Rescdsk
2
Ja, aber es gibt einen Fehler. Wenn Sie wie in diesem Beitrag eine Klammer in den Wert des Parameters "Name" einfügen, wird die Anzeige des Komponententestnamens in Eclipse unterbrochen.
Djangofan
7
toll, aber was ist wenn {0}und {1}sind Arrays? JUnit sollte im Idealfall nennt Arrays.toString({0}), nicht {0}.toString(). Zum Beispiel gibt meine data()Methode zurück Arrays.asList(new Object[][] {{ new int[] { 1, 3, 2 }, new int[] { 1, 2, 3 } }});.
Dogbane
1
@ Djangofan Dies ist ein 8 Jahre alter Eclipse-Fehler: bugs.eclipse.org/bugs/show_bug.cgi?id=102512
Pool
37

Bei JUnit 4.5 unterstützt der Runner dies eindeutig nicht, da diese Logik in einer privaten Klasse innerhalb der parametrisierten Klasse vergraben ist. Sie könnten den JUnit-parametrisierten Runner nicht verwenden und stattdessen einen eigenen erstellen, der das Konzept der Namen versteht (was zu der Frage führt, wie Sie einen Namen festlegen könnten ...).

Aus Sicht von JUnit wäre es schön, wenn anstelle (oder zusätzlich zu) der Übergabe eines Inkrements die durch Kommas getrennten Argumente übergeben würden. TestNG macht das. Wenn die Funktion für Sie wichtig ist, können Sie die Yahoo-Mailingliste kommentieren, auf die unter www.junit.org verwiesen wird.

Yishai
quelle
3
Ich würde mich sehr freuen, wenn es in JUnit eine Verbesserung dafür gibt!
Guerda
17
Gerade überprüft, gibt es eine ausstehende Feature-Anfrage dafür unter: github.com/KentBeck/junit/issues#issue/44 Bitte stimmen Sie ab.
reccles
8
@Frank, ich denke, dass die Version, die dieses Problem behebt, noch nicht veröffentlicht wurde. Es wird in JUnit 4.11 sein. Zu diesem Zeitpunkt (vorausgesetzt, das Design bleibt gleich) handelt es sich um eine textuelle Methode, mit der angegeben wird, wie Sie den Test benennen, einschließlich der Verwendung von Parametern als Namen. Eigentlich ziemlich nett.
Yishai
5
JUnit 4.11 wurde jetzt veröffentlicht :-)
Rescdsk
7
Hier ist der aktualisierte Link zur Originalausgabe github.com/junit-team/junit/issues/44 zur späteren Bezugnahme
kldavis4
20

Ich bin kürzlich auf dasselbe Problem gestoßen, als ich JUnit 4.3.1 verwendet habe. Ich habe eine neue Klasse implementiert, die Parameterized mit dem Namen LabelledParameterized erweitert. Es wurde mit JUnit 4.3.1, 4.4 und 4.5 getestet. Es rekonstruiert die Description-Instanz mithilfe der String-Darstellung des ersten Arguments jedes Parameterarrays aus der @ Parameters-Methode. Sie können den Code dafür sehen unter:

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

und ein Beispiel für seine Verwendung bei:

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

Die Testbeschreibung ist in Eclipse gut formatiert, was ich wollte, da dies das Auffinden fehlgeschlagener Tests erheblich erleichtert! Ich werde die Kurse wahrscheinlich in den nächsten Tagen / Wochen weiter verfeinern und dokumentieren. Lass fallen '?' Teil der URLs, wenn Sie die Blutungskante wollen. :-)

Um es zu verwenden, müssen Sie nur diese Klasse (GPL v3) kopieren und @RunWith (Parameterized.class) in @RunWith (LabelledParameterized.class) ändern, vorausgesetzt, das erste Element Ihrer Parameterliste ist eine sinnvolle Bezeichnung.

Ich weiß nicht, ob spätere Versionen von JUnit dieses Problem beheben, aber selbst wenn dies der Fall ist, kann ich JUnit nicht aktualisieren, da alle meine Mitentwickler ebenfalls aktualisieren müssten und wir höhere Prioritäten als das Nachrüsten haben. Daher muss die Arbeit in der Klasse von mehreren Versionen von JUnit kompiliert werden.


Hinweis: Es gibt einige Reflection Jiggery-Pokery, so dass es über die verschiedenen JUnit-Versionen läuft, wie oben aufgeführt. Die Version speziell für JUnit 4.3.1 finden Sie hier und für JUnit 4.4 und 4.5 hier .

Darrenp
quelle
:-) Einer meiner Mitentwickler hatte heute ein Problem damit, da die Version, auf die ich in der obigen Nachricht verweise, JUnit 4.3.1 verwendet (nicht 4.4, wie ich ursprünglich sagte). Er verwendet JUnit 4.5.0 und es hat Probleme verursacht. Ich werde diese heute ansprechen.
Darrenp
Ich brauchte einige Zeit , zu verstehen , dass Sie benötigen passieren die Testnamen im Konstruktor, aber nicht merkt es. Danke für den Code!
Giraff
Funktioniert hervorragend, solange ich die Tests von Eclipse aus durchführe. Hat jemand Erfahrung damit, dass es mit der JUnit Ant Task funktioniert? Die Testberichte werden execute[0], execute[1] ... execute[n]in den generierten Testberichten benannt .
Henrik Aasted Sørensen
Sehr schön. Klappt wunderbar. Wäre schön, wenn Sie die Info hinzufügen könnten, dass es erforderlich ist, "String label, ..." als ersten Parameter zur aufgerufenen @ Test-Methode hinzuzufügen.
Gia
13

Mit Parameterizedals Modell habe ich meinen eigenen Testläufer / meine eigene Test-Suite geschrieben - es dauerte nur etwa eine halbe Stunde. Es unterscheidet sich geringfügig von Darrenp LabelledParameterizeddarin, dass Sie einen Namen explizit angeben können, anstatt sich auf die ersten Parameter zu verlassen toString().

Es werden auch keine Arrays verwendet, weil ich Arrays hasse. :) :)

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);
    }
  }
}

Und ein Beispiel:

@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
quelle
6

Ab junit4.8.2 können Sie Ihre eigene MyParameterized-Klasse erstellen, indem Sie einfach die parametrisierte Klasse kopieren. Ändern Sie die Methoden getName () und testName () in TestClassRunnerForParameters.

Yliang
quelle
Ich habe es versucht, aber es hilft nicht. Beim Erstellen einer neuen Klasse schlägt getParametersMethod fehl.
Java_enthu
2

Sie können eine Methode wie erstellen

@Test
public void name() {
    Assert.assertEquals("", inboundFileName);
}

Obwohl ich es nicht die ganze Zeit benutzen würde, wäre es nützlich, genau herauszufinden, welche Testnummer 143 ist.


quelle
2

Ich verwende den statischen Import für Assert und Freunde in großem Umfang, sodass ich Assertion leicht neu definieren kann:

private <T> void assertThat(final T actual, final Matcher<T> expected) {
    Assert.assertThat(editThisToDisplaySomethingForYourDatum, actual, expected);
}

Sie können beispielsweise Ihrer Testklasse ein Feld "Name" hinzufügen, das im Konstruktor initialisiert wurde, und dieses Feld bei einem Testfehler anzeigen. Übergeben Sie es einfach als erstes Element Ihres Parameterarrays für jeden Test. Dies hilft auch beim Beschriften der Daten:

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
quelle
Dies ist in Ordnung, wenn der Test eine Bestätigung nicht besteht, aber es gibt andere Fälle, z. B. wenn eine Ausnahme ausgelöst wird, die den Test nicht besteht, oder wenn der Test erwartet, dass eine Ausnahme ausgelöst wird, die das Überlegen des Namensaufwands erforderlich macht vom Framework gehandhabt.
Yishai
2

Nichts davon funktionierte für mich, also habe ich die Quelle für Parametrisiert und modifiziert, um einen neuen Testläufer zu erstellen. Ich musste nicht viel ändern, aber es funktioniert !!!

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

Eine Problemumgehung besteht darin, alle Throwables mit einer benutzerdefinierten Nachricht zu fangen und in eine neue Throwable zu verschachteln, die alle Informationen zu den Parametern enthält. Die Nachricht wird im Stack-Trace angezeigt. Dies funktioniert immer dann, wenn ein Test für alle Zusicherungen, Fehler und Ausnahmen fehlschlägt, da sie alle Unterklassen von Throwable sind.

Mein Code sieht folgendermaßen aus:

@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);
        }
    }

}

Die Stapelverfolgung des fehlgeschlagenen Tests lautet:

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
quelle
0

Schauen Sie sich JUnitParams an, wie von dsaff erwähnt, und arbeiten Sie mit ant, um parametrisierte Testmethodenbeschreibungen im HTML-Bericht zu erstellen.

Dies geschah, nachdem LabelledParameterized ausprobiert wurde und festgestellt wurde, dass es, obwohl es mit Eclipse funktioniert, mit Ant nicht funktioniert, was den HTML-Bericht betrifft.

Prost,

Quarkonium
quelle
0

Da auf den Parameter zugegriffen wird (z. B. mit "{0}"immer die toString()Darstellung zurückgibt , besteht eine Problemumgehung darin, eine anonyme Implementierung vorzunehmen und jeweils zu überschreiben toString(). Beispiel:

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
quelle