Respuestas:
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 namestring
es {index}
.{0}
- el primer valor del parámetro de esta invocación de la prueba.{1}
- el segundo valor del parámetroEl nombre final de la prueba será el nombre del método de prueba, seguido de los namestring
paréntesis, como se muestra a continuación.
Por ejemplo (adaptado de la prueba unitaria para la Parameterized
anotació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 testFib
parte del nombre es el nombre del método de @Test
).
{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 } }});
.
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.
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í .
execute[0], execute[1] ... execute[n]
en los informes de prueba generados.
Con Parameterized
como modelo, escribí mi propio corredor / suite de prueba personalizado, solo me llevó aproximadamente media hora. Es ligeramente diferente de los de darrenp, ya LabelledParameterized
que 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);
}
}
desde junit4.8.2, puede crear su propia clase MyParameterized simplemente copiando la clase Parameterized. cambie los métodos getName () y testName () en TestClassRunnerForParameters.
También puede probar JUnitParams: http://code.google.com/p/junitparams/
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.
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}
});
}
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;
}
}
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
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,
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...
);
}