Zmiana nazw sparametryzowanych testów


204

Czy istnieje sposób ustawienia własnych nazw niestandardowych przypadków testowych podczas korzystania ze sparametryzowanych testów w JUnit4?

Chciałbym zmienić domyślną - [Test class].runTest[n]- na coś znaczącego.

Odpowiedzi:


299

Ta funkcja została wprowadzona do JUnit 4.11 .

Aby użyć zmień nazwę sparametryzowanych testów, powiedz:

@Parameters(name="namestring")

namestring jest ciągiem, który może mieć następujące specjalne symbole zastępcze:

  • {index}- indeks tego zestawu argumentów. Domyślnie namestringjest to {index}.
  • {0} - pierwsza wartość parametru z tego wywołania testu.
  • {1} - druga wartość parametru
  • i tak dalej

Ostateczna nazwa testu to nazwa metody testowej, po której następuje namestring w nawiasach, jak pokazano poniżej.

Na przykład (na podstawie testu jednostkowego Parameterizedadnotacji):

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

nada imiona takie jak testFib[1: fib(1)=1]i testFib[4: fib(4)=3]. ( testFibCzęść nazwy to nazwa metody @Test).


4
Nie ma powodu, by nie było go w 4.11, jest w trybie master. Teraz, kiedy 4.11 będzie dostępne, to dobre pytanie :-)
Matthew Farwell

1
4.11 jest teraz w wersji beta i można go pobrać z tego samego linku jak powyżej :-)
rescdsk

2
Tak, ale jest błąd. Jeśli umieścisz nawias w wartości parametru „name” tak, jak robisz to w tym ogłoszeniu, spowoduje to przerwanie wyświetlania nazwy testu jednostkowego w Eclipse.
djangofan

7
świetnie, ale co jeśli {0}i {1}są tablice? JUnit powinien idealnie zadzwonić Arrays.toString({0}), a nie {0}.toString(). Na przykład moja data()metoda zwraca Arrays.asList(new Object[][] {{ new int[] { 1, 3, 2 }, new int[] { 1, 2, 3 } }});.
dogbane

1
@djangofan To jest 8-letni błąd Eclipse: bugs.eclipse.org/bugs/show_bug.cgi?id=102512
Pula

37

Patrząc na JUnit 4.5, jego biegacz wyraźnie tego nie obsługuje, ponieważ logika ta jest zakopana w prywatnej klasie w klasie Sparametryzowanej. Nie można użyć narzędzia do parametryzacji JUnit i zamiast tego utworzyć własny, który zrozumiałby pojęcie nazw (co prowadzi do pytania, w jaki sposób można ustawić nazwę ...).

Z perspektywy JUnit byłoby miło, gdyby zamiast (lub oprócz) po prostu przekazać przyrost, przekazali argumenty rozdzielane przecinkami. TestNG to robi. Jeśli ta funkcja jest dla Ciebie ważna, możesz komentować listę dyskusyjną Yahoo, do której odwołuje się strona www.junit.org.


3
Byłbym bardzo wdzięczny, gdyby w JUnit nastąpiła poprawa!
guerda,

17
Właśnie sprawdzone, jest wyjątkowa prośba o tę funkcję na stronie: github.com/KentBeck/junit/issues#issue/44 Proszę zagłosować.
wspomina

8
@Frank, myślę, że wydanie, które rozwiązuje ten problem, nie zostało jeszcze wydane. Będzie w JUnit 4.11. W tym czasie (zakładając, że projekt pozostanie niezmieniony), będzie dotyczył tekstowego sposobu określania nazwy testu, w tym przyjmowania parametrów jako nazw. Właściwie całkiem niezłe.
Yishai

5
JUnit 4.11 został wydany :-)
rescdsk

7
Oto zaktualizowany link do oryginalnego wydania github.com/junit-team/junit/issues/44 do wglądu w przyszłości
kldavis4

20

Ostatnio natknąłem się na ten sam problem podczas korzystania z JUnit 4.3.1. Zaimplementowałem nową klasę, która rozszerza Parameterized o nazwie LabelledParameterized. Został przetestowany przy użyciu JUnit 4.3.1, 4.4 i 4.5. Odtwarza instancję Description za pomocą reprezentacji ciągu pierwszego argumentu każdej tablicy parametrów z metody @Parameters. Możesz zobaczyć kod tego na:

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

i przykład jego zastosowania w:

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

Opis testu ładnie formatuje się w Eclipse, co jest tym, czego chciałem, ponieważ dzięki temu nieudane testy są znacznie łatwiejsze do znalezienia! Prawdopodobnie będę dalej doskonalić i dokumentować zajęcia w ciągu najbliższych kilku dni / tygodni. Upuść '?' część adresów URL, jeśli chcesz mieć przewagę. :-)

Aby go użyć, wystarczy skopiować tę klasę (GPL v3) i zmienić @RunWith (Parameterized.class) na @RunWith (LabelledParameterized.class), zakładając, że pierwszy element listy parametrów jest sensowną etykietą.

Nie wiem, czy jakiekolwiek późniejsze wydania JUnit rozwiązują ten problem, ale nawet jeśli tak, nie mogę zaktualizować JUnit, ponieważ wszyscy moi programiści również musieliby zaktualizować i mamy wyższe priorytety niż ponowne oprzyrządowanie. Stąd praca w klasie, która ma być kompilowana przez wiele wersji JUnit.


Uwaga: istnieje pewien jiggery-pokery refleksji, dzięki czemu działa on w różnych wersjach JUnit wymienionych powyżej. Wersję specjalnie dla JUnit 4.3.1 można znaleźć tutaj, a dla JUnit 4.4 i 4.5 tutaj .


:-) Jeden z moich współtwórców miał dzisiaj z tym problem, ponieważ wersja, na którą wskazałem w powyższym komunikacie, używa JUnit 4.3.1 (nie 4.4, jak pierwotnie powiedziałem). Używa JUnit 4.5.0 i spowodowało to problemy. Zajmę się nimi dzisiaj.
darrenp

Poświęciłem trochę czasu, aby zrozumieć, że musisz przekazać nazwę testową do konstruktora, ale nie zapamiętywać jej. Dzięki za kod!
giraff

Działa świetnie, dopóki uruchamiam testy z Eclipse. Czy jednak ktoś ma doświadczenie w pracy z JUnit Ant Task? Raporty z testów są nazwane execute[0], execute[1] ... execute[n]w wygenerowanych raportach z testów.
Henrik Aasted Sørensen

Bardzo dobrze. Działa jak marzenie. Byłoby miło, gdybyś mógł dodać informację, że wymagane jest dodanie „Etykieta ciągu, ...” jako pierwszego parametru do wywoływanej metody @ Test.
gia,

13

Dzięki Parameterizedza wzór, napisałem mój własny niestandardowy testy biegacz / apartament - tylko trwało około pół godziny. Różni się nieco od darrenpa LabelledParameterizedtym, że pozwala jawnie określać nazwę zamiast polegać na pierwszym parametrze toString().

Nie używa również tablic, ponieważ nienawidzę tablic. :)

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

I przykład:

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

}

6

z junit4.8.2 możesz stworzyć własną klasę MyParameterized, po prostu kopiując Parameterized class. zmień metody getName () i testName () w TestClassRunnerForParameters.


Próbowałem tego, ale nie pomaga. Podczas tworzenia nowej klasy getParametersMethod kończy się niepowodzeniem.
java_enthu


2

Możesz stworzyć metodę podobną do

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

Chociaż nie używałbym go cały czas, dobrze byłoby dowiedzieć się, który test nr 143 jest.


2

W szerokim zakresie korzystam z importu statycznego dla Assert i przyjaciół, więc łatwo jest mi na nowo zdefiniować asercję:

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

Na przykład możesz dodać pole „name” do swojej klasy testowej, zainicjowane w konstruktorze i wyświetlić je w przypadku niepowodzenia testu. Wystarczy przekazać go jako pierwsze elementy tablicy parametrów dla każdego testu. Pomaga to również oznaczyć dane:

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

Jest to w porządku, jeśli test nie powiedzie się, ale istnieją inne przypadki, takie jak zgłoszenie wyjątku, który nie przejdzie testu, lub jeśli test oczekuje, że zostanie wygenerowany wyjątek, które przemyślą narzut nazwy, który powinien być obsługiwane przez ramy.
Yishai

2

Nic z tego nie działało, więc dostałem źródło dla Sparametryzowanego i zmodyfikowałem go, tworząc nowy tester. Nie musiałem wiele zmieniać, ale TO DZIAŁA !!!

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

Rozwiązaniem byłoby złapanie i zagnieżdżenie wszystkich Throwables w nowym Throwable z niestandardowym komunikatem, który zawiera wszystkie informacje o parametrach. Komunikat pojawi się w śladzie stosu. Działa to zawsze, gdy test kończy się niepowodzeniem dla wszystkich stwierdzeń, błędów i wyjątków, ponieważ wszystkie są podklasami Throwable.

Mój kod wygląda następująco:

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

}

Ślad stosu nieudanego testu to:

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

0

Sprawdź JUnitParams, jak wspomniano dsaff, działa przy użyciu ant, aby zbudować sparametryzowane opisy metod testowych w raporcie HTML.

Było to po wypróbowaniu LabelledParameterized i stwierdzeniu, że chociaż działa z zaćmieniem, nie działa z mrówką, jeśli chodzi o raport HTML.

Twoje zdrowie,


0

Ponieważ parametr, do którego uzyskano dostęp (np. Z "{0}"zawsze zwraca toString()reprezentację, jednym z obejść byłoby wykonanie anonimowej implementacji i zastąpienie toString()w każdym przypadku. Na przykład:

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...
    );
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.