Un modo semplice per eseguire lo stesso test junit più e più volte?


121

Come dice il titolo, sto cercando un modo semplice per eseguire i test di JUnit 4.x più volte di seguito automaticamente utilizzando Eclipse.

Un esempio potrebbe essere eseguire lo stesso test 10 volte di seguito e riportare il risultato.

Abbiamo già un modo complesso per farlo, ma sto cercando un modo semplice per farlo in modo da essere certo che il test traballante che ho cercato di risolvere rimanga fisso.

Una soluzione ideale sarebbe un plug-in / impostazione / funzionalità Eclipse di cui non sono a conoscenza.


5
Sono molto curioso di sapere perché vorresti farlo.
Buhb

Sto eseguendo un grande test della scatola nera, ho apportato una piccola modifica e voglio vedere come ciò ha influito sulla stabilità di questo test precedentemente traballante.
Stefan Thyberg,

Lo è davvero, tranne per il fatto che vuoi che funzioni fino al fallimento, mentre io voglio solo eseguirlo un numero di volte, il che potrebbe influenzare le risposte che ottengo.
Stefan Thyberg

4
Sei contro TestNG perché in caso contrario potresti semplicemente usare @Test (invocationCount = 10) e questo è tutto ciò che c'è da fare.
Robert Massaioli

1
Non ero "contro" TestNG, semplicemente non lo stavamo usando in quel progetto.
Stefan Thyberg

Risposte:


123

Il modo più semplice (come minimo quantità di nuovo codice richiesto) per farlo è eseguire il test come un test parametrizzato (annotare con un @RunWith(Parameterized.class) e aggiungere un metodo per fornire 10 parametri vuoti). In questo modo il framework eseguirà il test 10 volte.

Questo test dovrebbe essere l'unico test nella classe, o meglio mettere tutti i metodi di test dovrebbero essere eseguiti 10 volte nella classe.

Ecco un esempio:

@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 quanto sopra, è possibile farlo anche con un costruttore senza parametri, ma non sono sicuro se gli autori del framework lo intendessero o se ciò si interromperà in futuro.

Se stai implementando il tuo corridore, potresti fare in modo che il corridore esegua il test 10 volte. Se stai usando un runner di terze parti, allora con 4.7, puoi usare la nuova @Ruleannotazione e implementare l' MethodRuleinterfaccia in modo che prenda l'istruzione e la esegua 10 volte in un ciclo for. Lo svantaggio attuale di questo approccio è che @Beforee @Afterottenere eseguito solo una volta. Questo probabilmente cambierà nella prossima versione di JUnit ( @Beforeverrà eseguito dopo il @Rule), ma indipendentemente dal fatto che agirete sulla stessa istanza dell'oggetto (qualcosa che non è vero per il Parameterizedcorridore). Ciò presuppone che qualunque corridore con cui stai conducendo la classe riconosca correttamente le @Ruleannotazioni. Questo è solo il caso se sta delegando ai corridori JUnit.

Se stai correndo con un corridore personalizzato che non riconosce l' @Ruleannotazione, allora sei davvero bloccato nel dover scrivere il tuo corridore che delega in modo appropriato a quel corridore e lo esegue 10 volte.

Nota che ci sono altri modi per risolverlo potenzialmente (come il corridore di Theories) ma tutti richiedono un corridore. Sfortunatamente JUnit attualmente non supporta livelli di corridori. Questo è un corridore che incatena altri corridori.


2
Purtroppo sto già correndo @RunWith con un altro runner, ma altrimenti questa sarebbe stata una soluzione ideale.
Stefan Thyberg

Sì, questa è la soluzione che vorrei avere e che sarà la migliore per la maggior parte delle persone, quindi andrò avanti e accetterò la risposta.
Stefan Thyberg

Per una soluzione alternativa e forse meno hacker, vedere: stackoverflow.com/a/21349010/281545
Mr_and_Mrs_D

Bella soluzione! Ho ricevuto un'eccezione che mi diceva che il metodo dei dati dovrebbe restituire un Iterable di Arrays. L'ho corretto di conseguenza: @ Parameterized.Parameters public static Iterable <Object []> data () {return Arrays.asList (new Object [20] [0]); }
nadre

1
Potresti collegarti a questa risposta per JUnit 5? Descrive la funzione richiesta che è stata aggiunta in JUnit 5
Marcono1234

100

Con IntelliJ, puoi farlo dalla configurazione di prova. Una volta aperta questa finestra, puoi scegliere di eseguire il test tutte le volte che vuoi.

inserisci qui la descrizione dell'immagine

quando si esegue il test, intellij eseguirà tutti i test selezionati per il numero di volte specificato.

Esempio che esegue 624 test 10 volte: inserisci qui la descrizione dell'immagine


4
È perfetto, ora se puoi indicare un modo di eclissi per farlo, risponderebbe alla domanda dell'OP al punto
khal

Affidarsi a uno strumento specifico per ospitare la logica oi requisiti effettivi è un anti-pattern.
Mickael,

1
@Mickael Ripetere un test N volte di solito non è un requisito per il test. Infatti i test dovrebbero essere deterministici, in modo che non importa quante volte viene ripetuto, dovrebbe sempre produrre lo stesso risultato. Puoi spiegare l'anti pattern di cui parli?
smac89

Se la ripetizione di un test è stata utile per 1 sviluppatore, è probabile che sia utile per altri. Quindi, se il runtime di test e il codice possono ospitare la logica per abilitare la ripetizione, dovrebbe essere preferito in quanto consente di fattorizzare lo sforzo e la soluzione e consente ai contributori di utilizzare lo strumento desiderato con lo stesso risultato. Mettere la logica riutilizzabile nell'area IDE / sviluppatore quando può essere inserita nel codice è una sorta di fattorizzazione mancante.
Mickael

68

Ho scoperto che l'annotazione di ripetizione di Spring è utile per questo tipo di cose:

@Repeat(value = 10)

Documento più recente (Spring Framework 4.3.11.RELEASE API):


46
Cambiare i framework di test non è quello che chiamerei un modo semplice per farlo.
Stefan Thyberg,

3
Non è necessario modificare il framework di test: funziona bene con JUnit. Lo svantaggio principale è che JUnit lo vede ancora come un singolo test. Quindi la prima volta che si interrompe, l'esecuzione si interromperà. Tuttavia, se non stai già utilizzando Spring, probabilmente non è il modo in cui vorresti andare ...
tveon

Non sembra funzionare per me (Spring 4.3.6 tramite Spring Boot 1.5.1)
David Tonhofer

Non funziona per me con avvio a molla 2.1.6 e JUnit 5
jo

Funziona perfettamente con lo spring boot 2. Non dimenticare di aggiungere @RunWith (SpringRunner :: class), come da link "unit test in spring" del poster!
Agoston Horvath

33

Ispirato dalle seguenti risorse:

Esempio

Crea e utilizza @Repeatun'annotazione come segue:

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

L'utilizzo di questa soluzione con @RunWith(PowerMockRunner.class)richiede l'aggiornamento a Powermock 1.6.5 (che include una patch ).


Sì. Come stai eseguendo i test?
R. Oosterholt

Non sto usando eclipse da solo. Forse non stai usando un test runner Junut 4? (vedi documento "Personalizzazione di una configurazione di prova" )
R. Oosterholt

29

Con JUnit 5 sono stato in grado di risolvere questo problema utilizzando l' annotazione @RepeatedTest :

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

Nota che l' @Testannotazione non dovrebbe essere usata insieme a @RepeatedTest.


Sembra molto promettente, solo per notare che non esiste ancora una versione di rilascio.
9ilsdx 9rvj 0lo

1
JUnit 5 ha raggiunto GA un paio di settimane fa.
mkobit

11

Qualcosa di sbagliato in:

@Test
void itWorks() {
    // stuff
}

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

A differenza del caso in cui stai testando ciascuno di un array di valori, non ti interessa particolarmente quale esecuzione non è riuscita.

Non c'è bisogno di fare nella configurazione o nell'annotazione ciò che puoi fare nel codice.


2
Vorrei eseguire diversi test come normali unit test e ottenere una traccia e lo stato per ciascuno di essi.
Stefan Thyberg

24
In questo caso "@Before" e "@After" non verranno eseguiti
Bogdan

3
Questo insieme alla chiamata manuale del @Beforemetodo annotato prima di itWorks() risolvere il mio problema.
João Neves

Conosci il concetto DRY? en.wikipedia.org/wiki/Don%27t_repeat_yourself Consiglio di fare un po 'di configurazione invece di copiare e incollare il tuo loop ovunque.
Kikiwa,

La coda di modifica per questa risposta è piena; quindi, lo metto in un commento: per JUnit4, i test devono essere pubblici.
Richard Jessop,

7

Questo funziona molto più facilmente per me.

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

Fantastico perché non utilizza un altro framework e funziona effettivamente con JUnit 3 (cruciale per Android)
Vladimir Ivanov

1
Un'implementazione con JUnit4 potrebbe essere eseguita con un Runner: anche 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); } } }se almeno nel plugin Eclipse JUnit si ottengono risultati come: "10/1 test passati"
Peter Wippermann

7

C'è un'annotazione intermittente nella libreria tempus-fugit che funziona con JUnit 4.7 @Ruleper ripetere un test più volte o con@RunWith .

Per esempio,

@RunWith(IntermittentTestRunner.class)
public class IntermittentTestRunnerTest {

   private static int testCounter = 0;

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

Dopo che il test è stato eseguito (con IntermittentTestRunner in @RunWith), testCountersarebbe uguale a 99.


Sì, è lo stesso problema qui, sto già usando un altro corridore e quindi non posso usare questo, buona idea però.
Stefan Thyberg

Sì, ho lo stesso problema con RunWith ... mentre va ho modificato tempus-fugit per aggirare un po ', puoi usare una regola @ invece di un runner quando vuoi correre ripetutamente. Contrassegnalo con @Repeating invece che intermittente. Tuttavia, la versione della regola non verrà eseguita @ Before / @ Afters. Per ulteriori dettagli, visita tempus-fugit.googlecode.com/svn/site/documentation/… (scorri verso il basso per caricare / eseguire test).
Toby

0

Realizzo un modulo che permette di fare questo tipo di test. Ma è focalizzato non solo a ripetizione. Ma a garanzia che alcune parti di codice siano thread-safe.

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

Dipendenza da Maven:

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

Esempio di utilizzo:

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

Questo è un progetto open source. Sentiti libero di migliorare.


0

Puoi eseguire il tuo test JUnit da un metodo principale e ripeterlo tante volte di cui hai bisogno:

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

            }
        }

    }

}

0

Questa è essenzialmente la risposta che Yishai ha fornito sopra, riscritta in 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() { }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.