Cambio de nombres de pruebas parametrizadas

204

¿Hay alguna manera de configurar mis propios nombres de casos de prueba personalizados cuando uso pruebas parametrizadas en JUnit4?

Me gustaría cambiar el valor predeterminado [Test class].runTest[n]- a algo significativo.

Epaga
fuente

Respuestas:

300

Esta característica lo ha convertido en JUnit 4.11 .

Para usar cambiar el nombre de las pruebas parametrizadas, usted dice:

@Parameters(name="namestring")

namestring es una cadena, que puede tener los siguientes marcadores de posición especiales:

  • {index}- el índice de este conjunto de argumentos. El valor por defecto namestringes {index}.
  • {0} - el primer valor del parámetro de esta invocación de la prueba.
  • {1} - el segundo valor del parámetro
  • y así

El nombre final de la prueba será el nombre del método de prueba, seguido de los namestringparéntesis, como se muestra a continuación.

Por ejemplo (adaptado de la prueba unitaria para la Parameterizedanotación):

@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á nombres como testFib[1: fib(1)=1]y testFib[4: fib(4)=3]. (La testFibparte del nombre es el nombre del método de @Test).

rescdsk
fuente
44
No hay razón para que no esté en 4.11, está en master. Ahora, cuando 4.11 estará disponible, esa es una buena pregunta :-)
Matthew Farwell
1
4.11 ahora está en beta, y se puede descargar desde el mismo enlace que el anterior :-)
rescdsk
2
Sí, pero hay un error. Si coloca un paréntesis en el valor del parámetro "nombre" como lo está haciendo en esta publicación, se rompe la visualización del nombre de la prueba de unidad en Eclipse.
djangofan
77
genial, pero ¿ {0}y si {1}son matrices? Idealmente Arrays.toString({0}), JUnit debería llamar , no {0}.toString(). Por ejemplo, mi data()método regresa Arrays.asList(new Object[][] {{ new int[] { 1, 3, 2 }, new int[] { 1, 2, 3 } }});.
dogbane
1
@djangofan Este es un error de Eclipse de 8 años: bugs.eclipse.org/bugs/show_bug.cgi?id=102512
Pool
37

Mirando JUnit 4.5, su corredor claramente no lo admite, ya que esa lógica está enterrada dentro de una clase privada dentro de la clase Parameterized. No podría usar el corredor parametrizado JUnit, y crear uno propio que comprendería el concepto de nombres (lo que lleva a la pregunta de cómo podría establecer un nombre ...).

Desde una perspectiva JUnit, sería bueno si en lugar de (o además de) simplemente pasar un incremento, pasaran los argumentos delimitados por comas. TestNG hace esto. Si la función es importante para usted, puede comentar en la lista de correo de Yahoo a la que se hace referencia en www.junit.org.

Yishai
fuente
3
¡Apreciaría mucho si hay una mejora para esto en JUnit!
guerda
17
Recién verificado, hay una solicitud de función pendiente para esto en: github.com/KentBeck/junit/issues#issue/44 Por favor, vote.
Recicla
8
@ Frank, creo que la versión que aborda este problema aún no se ha publicado. Estará en JUnit 4.11. En ese momento (suponiendo que el diseño siga siendo el mismo) se tratará de una forma textual de especificar cómo se nombra la prueba, incluida la toma de parámetros como nombres. Bastante agradable, en realidad.
Yishai
55
JUnit 4.11 ahora se ha lanzado :-)
rescdsk
77
Aquí está el enlace actualizado al número original github.com/junit-team/junit/issues/44 para referencia futura
kldavis4
20

Recientemente me encontré con el mismo problema al usar JUnit 4.3.1. Implementé una nueva clase que extiende Parameterized llamada LabelledParameterized. Ha sido probado usando JUnit 4.3.1, 4.4 y 4.5. Reconstruye la instancia de Descripción utilizando la representación String del primer argumento de cada matriz de parámetros del método @Parameters. Puede ver el código para esto en:

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

y un ejemplo de su uso en:

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

¡La descripción de la prueba se forma muy bien en Eclipse, que es lo que quería, ya que esto hace que las pruebas fallidas sean mucho más fáciles de encontrar! Probablemente refinaré y documentaré más las clases en los próximos días / semanas. Suelta el '?' parte de las URL si desea el borde de sangrado. :-)

Para usarlo, todo lo que tiene que hacer es copiar esa clase (GPL v3) y cambiar @RunWith (Parameterized.class) a @RunWith (LabelledParameterized.class) asumiendo que el primer elemento de su lista de parámetros es una etiqueta sensible.

No sé si las versiones posteriores de JUnit abordan este problema, pero incluso si lo hicieran, no puedo actualizar JUnit ya que todos mis co-desarrolladores también tendrían que actualizar y tenemos prioridades más altas que volver a utilizar herramientas. De ahí que el trabajo en la clase sea compilable por múltiples versiones de JUnit.


Nota: hay algo de jiggery-pokery de reflexión para que se ejecute en las diferentes versiones de JUnit que se enumeran anteriormente. La versión específica para JUnit 4.3.1 se puede encontrar aquí y, para JUnit 4.4 y 4.5, aquí .

darrenp
fuente
:-) Uno de mis co-desarrolladores de hoy tuvo un problema ya que la versión a la que apunto en el mensaje anterior usa JUnit 4.3.1 (no 4.4 como dije originalmente). Está usando JUnit 4.5.0 y causó problemas. Me ocuparé de esto hoy.
darrenp
Me tomé un tiempo para comprender que debes pasar el nombre de la prueba en el constructor, pero no memorizarlo . ¡Gracias por el código!
giraff
Funciona muy bien siempre que ejecute las pruebas desde Eclipse. ¿Alguien tiene experiencia en hacer que funcione con la tarea de JUnit Ant? Los informes de prueba se nombran execute[0], execute[1] ... execute[n]en los informes de prueba generados.
Henrik Aasted Sørensen
Muy agradable. Funciona de maravilla. Sería bueno, si pudiera agregar la información, que se requiere agregar "Etiqueta de cadena, ..." como primer parámetro al método invocado @ Test.
gia
13

Con Parameterizedcomo modelo, escribí mi propio corredor / suite de prueba personalizado, solo me llevó aproximadamente media hora. Es ligeramente diferente de los de darrenp, ya LabelledParameterizedque le permite especificar un nombre explícitamente en lugar de depender de los primeros parámetros toString().

Tampoco usa matrices porque odio las matrices. :)

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

Y un ejemplo:

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

desde junit4.8.2, puede crear su propia clase MyParameterized simplemente copiando la clase Parameterized. cambie los métodos getName () y testName () en TestClassRunnerForParameters.

yliang
fuente
Intenté esto pero no ayuda. Al crear una nueva clase, getParametersMethod falla.
java_enthu
2

Puedes crear un método como

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

Si bien no lo usaría todo el tiempo, sería útil averiguar exactamente qué número de prueba es 143.


fuente
2

Hago un amplio uso de la importación estática para Assert y amigos, por lo que me resulta fácil redefinir la afirmación:

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

Por ejemplo, podría agregar un campo "nombre" a su clase de prueba, inicializado en el constructor, y mostrarlo en caso de falla de la prueba. Simplemente páselo como los primeros elementos de su matriz de parámetros para cada prueba. Esto también ayuda a etiquetar los datos:

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
fuente
Esto está bien si la prueba falla una afirmación, pero hay otros casos, como si se lanza una excepción que falla la prueba, o si la prueba espera que se lance una excepción, eso hace pensar en la sobrecarga del nombre que debería ser manejado por el marco.
Yishai
2

Nada de eso funcionaba para mí, así que obtuve la fuente de Parametrizado y lo modifiqué para crear un nuevo corredor de prueba. ¡No tuve que cambiar mucho pero 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;
}
}
cristiano
fuente
2

Una solución alternativa sería capturar y anidar todos los Throwables en un nuevo Throwable con un mensaje personalizado que contenga toda la información sobre los parámetros. El mensaje aparecería en el seguimiento de la pila. Esto funciona cuando falla una prueba para todas las aserciones, errores y excepciones, ya que todas son subclases de Throwable.

Mi código se ve así:

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

}

El seguimiento de la pila de la prueba fallida es:

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

Eche un vistazo a JUnitParams como se menciona en dsaff, funciona usando ant para construir descripciones de métodos de prueba parametrizados en el informe html.

Esto fue después de probar LabelledParameterized y descubrir que, aunque funciona con eclipse, no funciona con hormiga en lo que respecta al informe html.

Salud,

quarkonium
fuente
0

Dado que el parámetro al que se accede (por ejemplo, con "{0}"siempre devuelve la toString()representación, una solución alternativa sería realizar una implementación anónima y anular toString()en cada caso. Por ejemplo:

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
fuente