Alterando nomes de testes parametrizados

204

Existe uma maneira de definir meus próprios nomes de casos de teste personalizados ao usar testes parametrizados no JUnit4?

Eu gostaria de mudar o padrão - [Test class].runTest[n]- para algo significativo.

Epaga
fonte

Respostas:

299

Esse recurso entrou no JUnit 4.11 .

Para usar alterar o nome dos testes parametrizados, você diz:

@Parameters(name="namestring")

namestring é uma sequência, que pode ter os seguintes espaços reservados especiais:

  • {index}- o índice desse conjunto de argumentos. O padrão namestringé{index} .
  • {0} - o primeiro valor do parâmetro dessa invocação do teste.
  • {1} - o segundo valor do parâmetro
  • e assim por diante

O nome final do teste será o nome do método de teste, seguido pelos namestringparênteses, como mostrado abaixo.

Por exemplo (adaptado do teste de unidade para a Parameterizedanotação):

@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á nomes como testFib[1: fib(1)=1]e testFib[4: fib(4)=3]. (A testFibparte do nome é o nome do método do @Test).

rescdsk
fonte
4
Não há razão para não estar no 4.11, está no mestre. Agora, quando 4.11 estará disponível, que é uma boa pergunta :-)
Matthew Farwell
1
4.11 está agora em beta, e pode ser baixado a partir do mesmo link acima :-)
rescdsk
2
Sim, mas há um erro. Se você colocar um parêntese no valor do parâmetro "nome", como está fazendo nesta postagem, ele interromperá a exibição do nome do teste de unidade no Eclipse.
precisa
7
ótimo, mas {0}e se e {1}são matrizes? Idealmente Arrays.toString({0}), a JUnit deve chamar , não {0}.toString(). Por exemplo, meu data()método retorna Arrays.asList(new Object[][] {{ new int[] { 1, 3, 2 }, new int[] { 1, 2, 3 } }});.
dogbane
1
@djangofan Este é um bug do Eclipse de 8 anos: bugs.eclipse.org/bugs/show_bug.cgi?id=102512
Pool
37

Olhando para o JUnit 4.5, seu corredor claramente não suporta isso, pois essa lógica está oculta dentro de uma classe privada dentro da classe Parameterized. Você não pode usar o runner JUnit Parameterized e criar o seu próprio que entenderia o conceito de nomes (o que leva à questão de como você pode definir um nome ...).

Da perspectiva de JUnit, seria bom que, em vez de (ou além de) apenas passar um incremento, passassem os argumentos delimitados por vírgula. O TestNG faz isso. Se o recurso for importante para você, você pode comentar na lista de emails do yahoo mencionada em www.junit.org.

Yishai
fonte
3
Eu apreciaria muito se houver uma melhoria para isso no JUnit!
Guerda
17
Apenas verificado, há uma solicitação de recurso pendente para isso em: github.com/KentBeck/junit/issues#issue/44 Por favor vote.
reccles
8
@ Frank, acho que o lançamento que trata desse problema ainda não foi lançado. Ele estará na JUnit 4.11. Naquele momento (supondo que o design permaneça o mesmo), será sobre uma maneira textual de especificar como você nomeará o teste, incluindo a tomada de parâmetros como nomes. Muito bom, na verdade.
Yishai
5
JUnit 4,11 foi agora lançado :-)
rescdsk
7
Aqui está o link atualizado para a emissão original github.com/junit-team/junit/issues/44 para referência futura
kldavis4
20

Recentemente, deparei-me com o mesmo problema ao usar o JUnit 4.3.1. Eu implementei uma nova classe que estende Parameterized chamado LabelledParameterized. Foi testado usando o JUnit 4.3.1, 4.4 e 4.5. Ele reconstrói a instância Description usando a representação String do primeiro argumento de cada matriz de parâmetros do método @Parameters. Você pode ver o código para isso em:

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

e um exemplo de seu uso em:

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

A descrição do teste é bem formatada no Eclipse, o que eu queria, pois isso facilita muito a localização dos testes com falha! Provavelmente irei refinar e documentar as aulas nos próximos dias / semanas. Largue o '?' parte dos URLs, se você quiser o limite. :-)

Para usá-lo, basta copiar essa classe (GPL v3) e alterar @RunWith (Parameterized.class) para @RunWith (LabelledParameterized.class) assumindo que o primeiro elemento da sua lista de parâmetros seja um rótulo sensato.

Não sei se alguma versão posterior do JUnit aborda esse problema, mas mesmo se o fizesse, não consigo atualizar o JUnit, pois todos os meus co-desenvolvedores precisariam atualizar também e temos prioridades mais altas do que as re-ferramentas. Portanto, o trabalho na classe deve ser compilável por várias versões do JUnit.


Nota: existe um pouco de reflexão-piada para que seja executado nas diferentes versões do JUnit, conforme listado acima. A versão específica para o JUnit 4.3.1 pode ser encontrada aqui e, para o JUnit 4.4 e 4.5, aqui .

darrenp
fonte
:-) Um dos meus co-desenvolvedores de hoje teve um problema com ele, pois a versão que aponto na mensagem acima usa o JUnit 4.3.1 (não o 4.4 como eu disse originalmente). Ele está usando o JUnit 4.5.0 e isso causou problemas. Eu vou abordar isso hoje.
darrenp
Demorei um pouco para entender que você precisa passar o nome do teste no construtor, mas não memorizá- lo. Obrigado pelo código!
giraff
Funciona muito bem desde que eu execute os testes no Eclipse. Alguém tem experiência em fazê-lo funcionar com a Tarefa Ant do JUnit? Os relatórios de ensaio são nomeados execute[0], execute[1] ... execute[n]nos relatórios de teste gerados.
Henrik Aasted Sørensen 10/10
Muito agradável. Funciona como um encanto. Seria bom, se você pudesse adicionar as informações, que é necessário adicionar "String label, ..." como o primeiro parâmetro ao @ Test-method chamado.
gia
13

Com Parameterizedcomo um modelo, eu escrevi o meu próprio teste personalizado corredor / suite - só levou cerca de meia hora. É um pouco diferente do da darrenp, LabelledParameterizedpois permite especificar um nome explicitamente, em vez de depender do primeiro parâmetro toString().

Também não usa matrizes porque eu odeio matrizes. :)

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 um exemplo:

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

do junit4.8.2, você pode criar sua própria classe MyParameterized simplesmente copiando a classe Parameterized. altere os métodos getName () e testName () em TestClassRunnerForParameters.

yliang
fonte
Eu tentei isso, mas não ajuda. Ao criar a nova classe, getParametersMethod falha.
Java_enthu
2

Você pode criar um método como

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

Embora eu não usasse o tempo todo, seria útil descobrir exatamente qual é o número de teste 143.


fonte
2

Faço uso extensivo da importação estática para Assert e amigos, para que seja fácil redefinir a afirmação:

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

Por exemplo, você pode adicionar um campo "nome" à sua classe de teste, inicializada no construtor e exibi-lo na falha do teste. Apenas passe-o como os primeiros elementos da sua matriz de parâmetros para cada teste. Isso também ajuda a rotular os dados:

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
fonte
Isso é bom se o teste falhar em uma afirmação, mas há outros casos, como se uma exceção for lançada que falhar no teste ou se o teste estiver esperando que uma exceção seja lançada, que faça pensar no nome de sobrecarga que deve ser tratado pela estrutura.
Yishai 14/01
2

Nada disso estava funcionando para mim, então eu peguei a fonte para Parameterized e a modifiquei para criar um novo executor de teste. Eu não tive que mudar muito, mas FUNCIONA !!!

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;
}
}
cristão
fonte
2

Uma solução alternativa seria capturar e aninhar todos os Throwables em um novo Throwable com uma mensagem personalizada que contenha todas as informações sobre os parâmetros. A mensagem apareceria no rastreamento da pilha. Isso funciona sempre que um teste falha em todas as asserções, erros e exceções, pois todas são subclasses de Throwable.

Meu código fica assim:

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

}

O rastreamento de pilha do teste com falha é:

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

Confira JUnitParams como dsaff mencionado, trabalha usando ant para criar descrições parametrizadas de métodos de teste no relatório html.

Isso foi depois de tentar LabelledParameterized e descobrir que, embora funcione com o eclipse, não funciona com o ant no que diz respeito ao relatório html.

Felicidades,

quarkonium
fonte
0

Como o parâmetro acessado (por exemplo, "{0}"sempre retorna a toString()representação, uma solução alternativa seria fazer uma implementação anônima e substituir toString()em cada caso. Por exemplo:

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
fonte