¿Una manera fácil de ejecutar la misma prueba junit una y otra vez?

121

Como dice el título, estoy buscando una forma simple de ejecutar las pruebas JUnit 4.x varias veces seguidas automáticamente usando Eclipse.

Un ejemplo sería ejecutar la misma prueba 10 veces seguidas e informar el resultado.

Ya tenemos una forma compleja de hacer esto, pero estoy buscando una manera simple de hacerlo para poder estar seguro de que la prueba escamosa que he estado tratando de arreglar se mantiene fija.

Una solución ideal sería un complemento / configuración / característica de Eclipse que desconozco.

Stefan Thyberg
fuente
55
Tengo mucha curiosidad acerca de por qué querrías hacer esto.
Buhb
Estoy realizando una gran prueba de caja negra, he hecho un pequeño cambio y quiero ver cómo eso afectó la estabilidad de esta prueba previamente escamosa.
Stefan Thyberg
De hecho, lo es, excepto que desea que se ejecute hasta el fallo, mientras que solo quiero ejecutarlo varias veces, lo que puede afectar las respuestas que obtengo.
Stefan Thyberg
44
¿Estás en contra de TestNG porque si no, entonces podrías usar @Test (invocationCount = 10) y eso es todo lo que hay que hacer?
Robert Massaioli
1
No estaba "en contra" de TestNG, simplemente no lo estábamos usando en ese proyecto.
Stefan Thyberg

Respuestas:

123

La forma más fácil (como se requiere la menor cantidad de código nuevo) de hacer esto es ejecutar la prueba como una prueba parametrizada (anotar con @RunWith(Parameterized.class)y agregar un método para proporcionar 10 parámetros vacíos). De esa manera, el marco ejecutará la prueba 10 veces.

Esta prueba debería ser la única prueba en la clase, o mejor dicho, todos los métodos de prueba deberían ejecutarse 10 veces en la clase.

Aquí hay un ejemplo:

@RunWith(Parameterized.class)
public class RunTenTimes {

    @Parameterized.Parameters
    public static Object[][] data() {
        return new Object[10][0];
    }

    public RunTenTimes() {
    }

    @Test
    public void runsTenTimes() {
        System.out.println("run");
    }
}

Con lo anterior, es posible incluso hacerlo con un constructor sin parámetros, pero no estoy seguro de si los autores del marco lo pretendieron, o si eso se romperá en el futuro.

Si está implementando su propio corredor, puede hacer que el corredor ejecute la prueba 10 veces. Si está utilizando un corredor de terceros, entonces con 4.7, puede usar la nueva @Ruleanotación e implementar la MethodRuleinterfaz para que tome la declaración y la ejecute 10 veces en un ciclo for. El inconveniente actual de este enfoque es que @Beforey @Afterconseguir ejecutar sólo una vez. Es probable que esto cambie en la próxima versión de JUnit ( @Beforese ejecutará después de @Rule), pero independientemente de que va a actuar en la misma instancia del objeto (algo que no es cierto para el Parameterizedcorredor). Esto supone que cualquier corredor con el que esté ejecutando la clase reconoce correctamente las @Ruleanotaciones. Ese es solo el caso si está delegando a los corredores JUnit.

Si está ejecutando con un corredor personalizado que no reconoce la @Ruleanotación, entonces realmente tiene que escribir su propio corredor que delegue apropiadamente en ese corredor y lo ejecute 10 veces.

Tenga en cuenta que hay otras formas de resolver esto potencialmente (como el corredor de Teorías), pero todas requieren un corredor. Desafortunadamente, JUnit actualmente no admite capas de corredores. Ese es un corredor que encadena a otros corredores.

Yishai
fuente
2
Desafortunadamente, ya estoy ejecutando @RunWith con otro corredor, pero de lo contrario, esta habría sido una solución ideal.
Stefan Thyberg
Sí, esta es la solución que me gustaría tener y que será la mejor para la mayoría de las personas, así que voy a seguir adelante y aceptar la respuesta.
Stefan Thyberg 01 de
Para una solución alternativa y posiblemente menos hacky, consulte: stackoverflow.com/a/21349010/281545
Mr_and_Mrs_D el
Buena solución! Recibí una excepción que me dice que el método de datos debería devolver un Iterable de matrices. Lo arreglé en consecuencia: @ Parameterized.Parameters public static Iterable <Object []> data () {return Arrays.asList (new Object [20] [0]); }
nadre
1
¿Podría por favor vincular a esta respuesta para JUnit 5? Describe la función solicitada que se agregó en JUnit 5
Marcono1234 el
100

Con IntelliJ, puede hacer esto desde la configuración de prueba. Una vez que abra esta ventana, puede elegir ejecutar la prueba la cantidad de veces que desee.

ingrese la descripción de la imagen aquí

cuando ejecute la prueba, intellij ejecutará todas las pruebas que haya seleccionado para la cantidad de veces que especificó.

Ejemplo ejecutando 624 pruebas 10 veces: ingrese la descripción de la imagen aquí

smac89
fuente
44
Eso es perfecto, ahora si puede señalar una forma de eclipse de hacer esto, eso respondería la pregunta del OP al grano
khal
Confiar en una herramienta específica para alojar la lógica o los requisitos reales es un antipatrón.
Mickael
1
@Mickael Repetir una prueba N veces generalmente no es un requisito de prueba. De hecho, las pruebas deben ser deterministas, de modo que no importa cuántas veces se repita, siempre debe producir el mismo resultado. ¿Puedes explicar el anti patrón del que hablas?
smac89
Si repetir una prueba ha sido útil para 1 desarrollador, es probable que sea útil para otros. Entonces, si el tiempo de ejecución de la prueba y el código pueden alojar la lógica para permitir la repetición, debería preferirse ya que permite factorizar el esfuerzo y la solución y permite a los contribuyentes usar la herramienta que quieran con el mismo resultado. Poner lógica reutilizable en el área IDE / desarrollador cuando se puede poner en el código es una especie de factorización faltante.
Mickael
68

He descubierto que la anotación repetida de Spring es útil para ese tipo de cosas:

@Repeat(value = 10)

Último documento (Spring Framework 4.3.11.RELEASE API):

laura
fuente
46
Cambiar los marcos de prueba no es lo que yo llamaría una forma fácil de hacerlo.
Stefan Thyberg
3
No necesita cambiar su marco de prueba, funciona bien con JUnit. El principal inconveniente es que JUnit todavía lo ve como una sola prueba. Entonces, la primera vez que se rompe, la ejecución se detendrá. Sin embargo, si usted no está utilizando la primavera, entonces probablemente no es la forma en que quiere ir ...
tveon
No parece funcionar para mí (Spring 4.3.6 a través de Spring Boot 1.5.1)
David Tonhofer
No funciona para mí con el arranque de primavera 2.1.6 y el 5 de junio
jo-
Funciona perfectamente con el arranque de primavera 2. ¡No olvides agregar @RunWith (SpringRunner :: class), según el enlace 'prueba de unidad en primavera' del póster!
Agoston Horvath
33

Inspirado en los siguientes recursos:

Ejemplo

Cree y use una @Repeatanotación de la siguiente manera:

public class MyTestClass {

    @Rule
    public RepeatRule repeatRule = new RepeatRule();

    @Test
    @Repeat(10)
    public void testMyCode() {
        //your test code goes here
    }
}

Repeat.java

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.METHOD;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention( RetentionPolicy.RUNTIME )
@Target({ METHOD, ANNOTATION_TYPE })
public @interface Repeat {
    int value() default 1;
}

RepeatRule.java

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

public class RepeatRule implements TestRule {

    private static class RepeatStatement extends Statement {
        private final Statement statement;
        private final int repeat;    

        public RepeatStatement(Statement statement, int repeat) {
            this.statement = statement;
            this.repeat = repeat;
        }

        @Override
        public void evaluate() throws Throwable {
            for (int i = 0; i < repeat; i++) {
                statement.evaluate();
            }
        }

    }

    @Override
    public Statement apply(Statement statement, Description description) {
        Statement result = statement;
        Repeat repeat = description.getAnnotation(Repeat.class);
        if (repeat != null) {
            int times = repeat.value();
            result = new RepeatStatement(statement, times);
        }
        return result;
    }
}

PowerMock

Usar esta solución con @RunWith(PowerMockRunner.class), requiere actualizar a Powermock 1.6.5 (que incluye un parche ).

R. Oosterholt
fuente
Si. ¿Cómo estás ejecutando las pruebas?
R. Oosterholt
No estoy usando eclipse yo mismo. ¿Quizás no estás usando un corredor de prueba del 4 de junio? (véase el documento "Personalización de una configuración de prueba" )
R. Oosterholt
29

Con JUnit 5 pude resolver esto usando la anotación @RepeatedTest :

@RepeatedTest(10)
public void testMyCode() {
    //your test code goes here
}

Tenga en cuenta que la @Testanotación no debe usarse junto con @RepeatedTest.

César Alberca
fuente
Suena muy prometedor, solo para notar que aún no hay una versión de lanzamiento.
9ilsdx 9rvj 0lo
1
JUnit 5 llegó a GA hace un par de semanas.
mkobit
11

Algo mal con:

@Test
void itWorks() {
    // stuff
}

@Test
void itWorksRepeatably() {
    for (int i = 0; i < 10; i++) {
        itWorks();
    }
}

A diferencia del caso en el que está probando cada una de una matriz de valores, no le importa particularmente qué ejecución falló.

No es necesario hacer en la configuración o anotación lo que puede hacer en el código.

soru
fuente
2
Me gustaría ejecutar varias pruebas como pruebas unitarias normales y obtener un seguimiento y estado de cada una.
Stefan Thyberg 01 de
24
En este caso, no se ejecutarán "@Before" y "@After" s
Bogdan
3
Esto junto con llamar manualmente al @Beforemétodo anotado antes de itWorks() resolver mi problema.
João Neves
¿Conoces el concepto DRY? en.wikipedia.org/wiki/Don%27t_repeat_yourself Recomiendo hacer una configuración en lugar de copiar y pegar el bucle en todas partes.
Kikiwa
La cola de edición para esta respuesta está llena; por lo tanto, lo pondré en un comentario: para JUnit4, las pruebas deben ser públicas.
Richard Jessop
7

Esto funciona mucho más fácil para mí.

public class RepeatTests extends TestCase {

    public static Test suite() {
        TestSuite suite = new TestSuite(RepeatTests.class.getName());

        for (int i = 0; i < 10; i++) {              
        suite.addTestSuite(YourTest.class);             
        }

        return suite;
    }
}
Qualk
fuente
Impresionante ya que no usa otro marco y realmente funciona con JUnit 3 (crucial para Android)
Vladimir Ivanov
1
Una implementación con JUnit4 podría hacerse con un Runner: public class RepeatRunner extends BlockJUnit4ClassRunner { public RepeatRunner(Class klass) throws InitializationError { super(klass); } @Override public void run(final RunNotifier notifier) { for (int i = 0; i < 10; i++) { super.run(notifier); } } }aunque al menos en el complemento Eclipse JUnit obtienes resultados como: "10/1 pruebas aprobadas"
Peter Wippermann
7

Hay una anotación intermitente en la biblioteca tempus-fugit que funciona con JUnit 4.7 @Rulepara repetir una prueba varias veces o con @RunWith.

Por ejemplo,

@RunWith(IntermittentTestRunner.class)
public class IntermittentTestRunnerTest {

   private static int testCounter = 0;

   @Test
   @Intermittent(repition = 99)
   public void annotatedTest() {
      testCounter++;
   }
}

Después de ejecutar la prueba (con IntermittentTestRunner en el @RunWith), testCountersería igual a 99.

Toby
fuente
Sí, es el mismo problema aquí, ya estoy usando otro corredor y no puedo usar este, aunque es una buena idea.
Stefan Thyberg
Sí, estoy teniendo el mismo problema con RunWith ... a medida que avanzaba, modifiqué tempus-fugit para evitarlo un poco, puedes usar una @Rule en lugar de un corredor cuando quieres correr repetidamente. Lo marca con @Repeating en lugar de intermitente. Sin embargo, la versión de la regla no se ejecutará @ Before / @ Afters. Consulte tempus-fugit.googlecode.com/svn/site/documentation/… (desplácese hacia abajo para cargar / pruebas de remojo) para obtener más detalles.
Toby
0

Construyo un módulo que permite hacer este tipo de pruebas. Pero está enfocado no solo en repetición. Pero como garantía de que algún código es seguro para subprocesos.

https://github.com/anderson-marques/concurrent-testing

Dependencia de Maven:

<dependency>
    <groupId>org.lite</groupId>
    <artifactId>concurrent-testing</artifactId>
    <version>1.0.0</version>
</dependency>

Ejemplo de uso:

package org.lite.concurrent.testing;

import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import ConcurrentTest;
import ConcurrentTestsRule;

/**
 * Concurrent tests examples
 */
public class ExampleTest {

    /**
     * Create a new TestRule that will be applied to all tests
     */
    @Rule
    public ConcurrentTestsRule ct = ConcurrentTestsRule.silentTests();

    /**
     * Tests using 10 threads and make 20 requests. This means until 10 simultaneous requests.
     */
    @Test
    @ConcurrentTest(requests = 20, threads = 10)
    public void testConcurrentExecutionSuccess(){
        Assert.assertTrue(true);
    }

    /**
     * Tests using 10 threads and make 20 requests. This means until 10 simultaneous requests.
     */
    @Test
    @ConcurrentTest(requests = 200, threads = 10, timeoutMillis = 100)
    public void testConcurrentExecutionSuccessWaitOnly100Millissecond(){
    }

    @Test(expected = RuntimeException.class)
    @ConcurrentTest(requests = 3)
    public void testConcurrentExecutionFail(){
        throw new RuntimeException("Fail");
    }
}

Este es un proyecto de código abierto. Siéntase libre de mejorar.

Anderson Marques
fuente
0

Puede ejecutar su prueba JUnit desde un método principal y repetirla tantas veces como sea necesario:

package tests;

import static org.junit.Assert.*;

import org.junit.Test;
import org.junit.runner.Result;

public class RepeatedTest {

    @Test
    public void test() {
        fail("Not yet implemented");
    }

    public static void main(String args[]) {

        boolean runForever = true;

        while (runForever) {
            Result result = org.junit.runner.JUnitCore.runClasses(RepeatedTest.class);

            if (result.getFailureCount() > 0) {
                runForever = false;
               //Do something with the result object

            }
        }

    }

}
silver_mx
fuente
0

Esta es esencialmente la respuesta que Yishai proporcionó anteriormente, reescrita en Kotlin:

@RunWith(Parameterized::class)
class MyTest {

    companion object {

        private const val numberOfTests = 200

        @JvmStatic
        @Parameterized.Parameters
        fun data(): Array<Array<Any?>> = Array(numberOfTests) { arrayOfNulls<Any?>(0) }
    }

    @Test
    fun testSomething() { }
}
marca
fuente