Modifica dei nomi dei test con parametri


204

Esiste un modo per impostare i miei nomi di casi di test personalizzati quando si utilizzano test con parametri in JUnit4?

Vorrei cambiare il valore predefinito - [Test class].runTest[n]- in qualcosa di significativo.

Risposte:


299

Questa funzione è diventata JUnit 4.11 .

Per usare cambia il nome dei test con parametri, dici:

@Parameters(name="namestring")

namestring è una stringa che può avere i seguenti segnaposti speciali:

  • {index}- l'indice di questa serie di argomenti. L'impostazione predefinita namestringè {index}.
  • {0} - il primo valore del parametro da questa chiamata al test.
  • {1} - il secondo valore del parametro
  • e così via

Il nome finale del test sarà il nome del metodo di test, seguito dalle namestringparentesi quadre, come mostrato di seguito.

Ad esempio (adattato dal test unitario per l' Parameterizedannotazione):

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

darà nomi come testFib[1: fib(1)=1]e testFib[4: fib(4)=3]. (La testFibparte del nome è il nome del metodo di @Test).


4
Non c'è motivo per cui non sarebbe in 4.11, è in master. Ora, quando sarà disponibile 4.11, questa è una buona domanda :-)
Matthew Farwell,

1
4.11 è ora in versione beta e può essere scaricato dallo stesso link di cui sopra :-)
rescdsk,

2
Sì, ma c'è un bug. Se si inserisce una parentesi nel valore del "nome" del parametro come si fa in questo post, interrompe la visualizzazione del nome del test unitario in Eclipse.
Djangofan,

7
fantastico, ma cosa succede se {0}e {1}sono array? JUnit dovrebbe idealmente chiamare Arrays.toString({0}), no {0}.toString(). Ad esempio, il mio data()metodo ritorna Arrays.asList(new Object[][] {{ new int[] { 1, 3, 2 }, new int[] { 1, 2, 3 } }});.
dogbane,

1
@djangofan Questo è un bug Eclipse di 8 anni: bugs.eclipse.org/bugs/show_bug.cgi?id=102512
Pool

37

Guardando JUnit 4.5, il suo runner chiaramente non lo supporta, poiché quella logica è sepolta all'interno di una classe privata all'interno della classe Parameterized. Non è possibile utilizzare il runner con parametri JUnit e crearne uno proprio in grado di comprendere il concetto di nomi (che porta alla domanda su come impostare un nome ...).

Dal punto di vista di JUnit, sarebbe bello se invece (o in aggiunta a) solo passando un incremento, passassero gli argomenti delimitati da virgole. TestNG fa questo. Se la funzione è importante per te, puoi commentare la mailing list di yahoo a cui si fa riferimento su www.junit.org.


3
Gradirei molto se ci fosse un miglioramento per questo in JUnit!
guerda,

17
Appena verificato, c'è una richiesta di funzionalità eccezionale per questo su: github.com/KentBeck/junit/issues#issue/44 Per favore votalo .
recc

8
@Frank, penso che la versione che risolve questo problema non sia ancora stata rilasciata. Sarà in JUnit 4.11. In quel momento (supponendo che il design rimanga lo stesso) si tratterà di un modo testuale di specificare come nominare il test, incluso prendere i parametri come nomi. Molto carino, in realtà.
Yishai,

5
JUnit 4.11 è stato ora rilasciato :-)
rescdsk il

7
Ecco il link aggiornato al numero originale github.com/junit-team/junit/issues/44 per riferimento futuro
kldavis4

20

Di recente ho riscontrato lo stesso problema durante l'utilizzo di JUnit 4.3.1. Ho implementato una nuova classe che estende Parameterized chiamato LabelledParameterized. È stato testato utilizzando JUnit 4.3.1, 4.4 e 4.5. Ricostruisce l'istanza Description usando la rappresentazione String del primo argomento di ciascun array di parametri dal metodo @Parameters. Puoi vedere il codice per questo su:

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

e un esempio del suo utilizzo in:

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

La descrizione del test si adatta perfettamente a Eclipse, che è quello che volevo poiché questo rende i test falliti molto più facili da trovare! Probabilmente perfezionerò e documenterò ulteriormente le lezioni nei prossimi giorni / settimane. Rilascia il "?" parte degli URL se si desidera il limite massimo. :-)

Per usarlo, tutto ciò che devi fare è copiare quella classe (GPL v3) e cambiare @RunWith (Parameterized.class) in @RunWith (LabelledParameterized.class) supponendo che il primo elemento dell'elenco dei parametri sia un'etichetta sensibile.

Non so se eventuali versioni successive di JUnit risolvano questo problema, ma anche se lo facessero, non posso aggiornare JUnit poiché tutti i miei co-sviluppatori dovrebbero aggiornare anche noi e abbiamo priorità più alte rispetto al re-tooling. Da qui il lavoro nella classe per essere compilabile da più versioni di JUnit.


Nota: esiste un po 'di jiggery-pokery di riflessione in modo che scorra attraverso le diverse versioni di JUnit come elencato sopra. La versione specifica per JUnit 4.3.1 è disponibile qui e, per JUnit 4.4 e 4.5, qui .


:-) Uno dei miei co-sviluppatori oggi ha avuto un problema con esso poiché la versione a cui faccio riferimento nel messaggio sopra usa JUnit 4.3.1 (non 4.4 come ho detto inizialmente). Sta usando JUnit 4.5.0 e questo ha causato problemi. Mi occuperò di questi oggi.
darrenp,

Ho impiegato del tempo per capire che è necessario passare il nome del test nel costruttore, ma non memorizzarlo . Grazie per il codice!
Giraff,

Funziona alla grande finché eseguo i test da Eclipse. Qualcuno ha esperienza con il farlo funzionare con l'attività Ant JUnit, però? I rapporti di prova sono indicati execute[0], execute[1] ... execute[n]nei rapporti di prova generati.
Henrik Aasted Sørensen,

Molto bella. Funziona come un fascino. Sarebbe bello, se potessi aggiungere le informazioni, che è necessario aggiungere "Etichetta stringa, ..." come primo parametro al metodo @ Test invocato.
gia

13

Con Parameterizedcome modello, ho scritto il mio runner / suite di test personalizzato - ho impiegato solo circa mezz'ora. È leggermente diverso da quello di Darrenp LabelledParameterizedin quanto consente di specificare un nome in modo esplicito anziché basarsi sul primo parametrotoString() .

Inoltre non usa le matrici perché odio le matrici. :)

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

E un esempio:

@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

da junit4.8.2, puoi creare la tua classe MyParameterized semplicemente copiando la classe Parameterized. modificare i metodi getName () e testName () in TestClassRunnerForParameters.


Ho provato questo, ma non aiuta. Durante la creazione di una nuova classe getParametersMethod non riesce.
java_enthu,


2

Puoi creare un metodo come

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

Anche se non lo userei sempre, sarebbe utile capire esattamente quale sia il numero di test 143.


2

Faccio ampio uso dell'importazione statica per Assert e gli amici, quindi è facile per me ridefinire l'affermazione:

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

Ad esempio, è possibile aggiungere un campo "name" alla classe di test, inizializzato nel costruttore e visualizzarlo in caso di errore del test. Passalo semplicemente come primo elemento dell'array di parametri per ogni test. Questo aiuta anche a etichettare i dati:

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

Questo va bene se il test fallisce un'asserzione, ma ci sono altri casi, come se viene generata un'eccezione che fallisce il test, o se il test si aspetta che venga generata un'eccezione, che fa pensare al nome ambientale che dovrebbe essere gestito dal framework.
Yishai,

2

Niente di tutto ciò funzionava per me, quindi ho ottenuto la fonte per Parameterized e l'ho modificato per creare un nuovo test runner. Non ho dovuto cambiare molto ma FUNZIONA !!!

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

Una soluzione alternativa sarebbe quella di catturare e annidare tutti i Throwable in un nuovo Throwable con un messaggio personalizzato che contiene tutte le informazioni sui parametri. Il messaggio apparirà nella traccia dello stack. Funziona ogni volta che un test fallisce per tutte le asserzioni, errori ed eccezioni in quanto sono tutte le sottoclassi di Gettabile.

Il mio codice è simile al seguente:

@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 traccia dello stack del test non riuscito è:

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

Dai un'occhiata a JUnitParams come accennato da dsaff, funziona con ant per creare descrizioni dei metodi di prova con parametri nel rapporto html.

Questo dopo aver provato LabelledParameterized e aver scoperto che sebbene funzioni con Eclipse, non funziona con ant per quanto riguarda il rapporto HTML.

Saluti,


0

Poiché il parametro cui si accede (ad es. Con "{0}"restituisce sempre la toString()rappresentazione, una soluzione alternativa consisterebbe nel realizzare un'implementazione anonima e sovrascrivere toString()in ciascun caso. Ad esempio:

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...
    );
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.