Alterando nomes de testes parametrizados

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.

    Este recurso entrou no JUnit 4.11 .

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

     @Parameters(name="namestring") 

    namestring é uma string, que pode ter os seguintes marcadores especiais:

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

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

    Por exemplo (adaptado do teste unitário para a anotação Parameterized ):

     @RunWith(Parameterized.class) static public class FibonacciTest { @Parameters( name = "{index}: fib({0})={1}" ) public static Iterable 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 parte testFib do nome é o nome do método do @Test ).

    Olhando para o JUnit 4.5, seu runner claramente não suporta isso, já que essa lógica está enterrada dentro de uma class privada dentro da class 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 …).

    De uma perspectiva JUnit, seria bom se, em vez de (ou além de) passar um incremento, eles passassem os argumentos delimitados por vírgula. TestNG faz isso. Se o recurso for importante para você, você pode comentar na lista de discussão do yahoo mencionada em http://www.junit.org.

    Recentemente me deparei com o mesmo problema ao usar o JUnit 4.3.1. Eu implementei uma nova class que estende Parameterized chamado LabelledParameterized. Foi testado usando 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 formata muito bem no Eclipse, que é o que eu queria, pois isso torna os testes com falhas muito mais fáceis de encontrar! Eu provavelmente refinarei ainda mais e documentarei as aulas nos próximos dias / semanas. Largue o ‘?’ parte das URLs, se você quiser a borda do sangramento. 🙂

    Para usá-lo, tudo que você precisa fazer é copiar essa class (GPL v3) e alterar @RunWith (Parameterized.class) para @RunWith (LabelledParameterized.class), assumindo que o primeiro elemento da sua lista de parâmetros seja um label sensato.

    Eu não sei se quaisquer versões posteriores do JUnit resolvem este problema, mas mesmo se o fizessem, eu não posso atualizar o JUnit já que todos os meus co-desenvolvedores teriam que atualizar também e nós temos prioridades mais altas do que o re-tooling. Daí o trabalho na class para ser compilado por várias versões do JUnit.


    Nota: há algum jiggery-pokery de reflection para que ele seja executado nas diferentes versões do JUnit, conforme listado acima. A versão específica para JUnit 4.3.1 pode ser encontrada aqui e, para JUnit 4.4 e 4.5, aqui .

    Com o Parameterized como modelo, escrevi meu próprio test runner / suite personalizado – demorei apenas meia hora. É um pouco diferente do LabelledParameterized de LabelledParameterized na LabelledParameterized em que permite especificar um nome explicitamente, em vez de confiar no primeiro parâmetro de toString() .

    Ele 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 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.emptyList()); TestClass testClass = getTestClass(); Class< ?> jTestClass = testClass.getJavaClass(); Configuration configuration = getConfiguration(testClass); List runners = new ArrayList(); 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 getChildren() { return runners; } // ////////////////////////////// // Private private Configuration getConfiguration(TestClass testClass) throws Throwable { return (Configuration) getConfigMethod(testClass).invokeExplosively(null); } private FrameworkMethod getConfigMethod(TestClass testClass) { List 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 classs 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 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); } } 

    A partir do junit4.8.2, você pode criar sua própria class MyParameterized simplesmente copiando a class Parameterized. Altere os methods getName () e testName () em TestClassRunnerForParameters.

    Você também pode querer experimentar o JUnitParams: http://code.google.com/p/junitparams/

    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.

    Eu faço uso extensivo de importação estática para Assert e amigos, então é fácil redefinir a asserção:

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

    Por exemplo, você poderia adicionar um campo “name” à sua class de teste, inicializado no construtor, e exibir isso na falha do teste. Basta passá-lo como os primeiros elementos de 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 data() { return asList(new Object[][]{ {"first test", 3, 4}, {"second test", 5, 6} }); } 

    Nada disso estava funcionando para mim, então eu peguei a fonte para Parameterized e modifiquei para criar um novo test runner. 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 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 eachOne(Object... params) { List results = new ArrayList(); for (Object param : params) results.add(new Object[] { param }); return results; } } 

    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 de pilha. Isso funciona sempre que um teste falha para todas as asserções, erros e exceções, já que são todas subclasss de Throwable.

    Meu código é assim:

     @RunWith(Parameterized.class) public class ParameterizedTest { int parameter; public ParameterizedTest(int parameter) { super(); this.parameter = parameter; } @Parameters public static Collection 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 

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

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

    Felicidades,

    Como o parâmetro acessado (por exemplo, com "{0}" sempre retorna a representação toString() , uma solução alternativa seria fazer uma implementação anônima e replace 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... ); }