Un moyen facile d'exécuter le même test junit encore et encore?

121

Comme le titre l'indique, je cherche un moyen simple d'exécuter des tests JUnit 4.x plusieurs fois de suite automatiquement en utilisant Eclipse.

Un exemple serait d'exécuter le même test 10 fois de suite et de rendre compte du résultat.

Nous avons déjà une façon complexe de faire cela, mais je cherche une façon simple de le faire afin que je puisse être certain que le test irrégulier que j'ai essayé de corriger reste fixe.

Une solution idéale serait un plugin / paramètre / fonctionnalité Eclipse que je ne connais pas.

Stefan Thyberg
la source
5
Je suis très curieux de savoir pourquoi vous voudriez faire cela.
Buhb le
J'exécute un gros test de boîte noire, j'ai fait un petit changement et je veux voir comment cela a affecté la stabilité de ce test auparavant irrégulier.
Stefan Thyberg le
C'est en effet, sauf que vous voulez qu'il fonctionne jusqu'à l'échec, alors que je veux juste l'exécuter un certain nombre de fois, ce qui peut affecter les réponses que j'obtiens.
Stefan Thyberg
4
Êtes-vous contre TestNG car sinon, vous pouvez simplement utiliser @Test (invocationCount = 10) et c'est tout ce qu'il y a à faire.
Robert Massaioli
1
Je n'étais pas "contre" TestNG, nous ne l'utilisions tout simplement pas dans ce projet.
Stefan Thyberg le

Réponses:

123

Le moyen le plus simple (avec le moins de nouveau code requis) pour ce faire est d'exécuter le test en tant que test paramétré (annoter avec @RunWith(Parameterized.class)et ajouter une méthode pour fournir 10 paramètres vides). De cette façon, le framework exécutera le test 10 fois.

Ce test devrait être le seul test de la classe, ou mieux, toutes les méthodes de test devraient être exécutées 10 fois dans la classe.

Voici un exemple:

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

Avec ce qui précède, il est même possible de le faire avec un constructeur sans paramètre, mais je ne suis pas sûr si les auteurs du framework l'ont voulu, ou si cela se cassera à l'avenir.

Si vous implémentez votre propre coureur, vous pouvez demander au coureur d'exécuter le test 10 fois. Si vous utilisez un runner tiers, avec 4.7, vous pouvez utiliser la nouvelle @Ruleannotation et implémenter l' MethodRuleinterface afin qu'elle prenne l'instruction et l'exécute 10 fois dans une boucle for. L'inconvénient actuel de cette approche est que @Beforeet @Afters'exécuter qu'une seule fois. Cela changera probablement dans la prochaine version de JUnit (le @Beforefonctionnera après le @Rule), mais quoi qu'il en soit, vous agirez sur la même instance de l'objet (ce qui n'est pas vrai pour le Parameterizedcoureur). Cela suppose que le coureur avec lequel vous exécutez la classe reconnaît correctement les @Ruleannotations. Ce n'est le cas que s'il délègue aux coureurs JUnit.

Si vous exécutez avec un coureur personnalisé qui ne reconnaît pas l' @Ruleannotation, vous êtes vraiment obligé d'écrire votre propre coureur qui délègue de manière appropriée à ce coureur et l'exécute 10 fois.

Notez qu'il existe d'autres moyens de résoudre ce problème (comme le runner Theories), mais ils nécessitent tous un runner. Malheureusement, JUnit ne prend actuellement pas en charge les couches de coureurs. C'est un coureur qui enchaîne d'autres coureurs.

Yishai
la source
2
Malheureusement, je lance déjà @RunWith avec un autre coureur, mais sinon, cela aurait été une solution idéale.
Stefan Thyberg
Oui, c'est la solution que j'aimerais avoir et qui sera la meilleure pour la plupart des gens, alors je vais aller de l'avant et accepter la réponse.
Stefan Thyberg
Pour une solution alternative et peut-être moins piratée, voir: stackoverflow.com/a/21349010/281545
Mr_and_Mrs_D
Belle solution! J'ai eu une exception me disant que la méthode de données devrait retourner un Iterable of Arrays. Je l'ai corrigé en conséquence: @ Parameterized.Parameters public static Iterable <Object []> data () {return Arrays.asList (new Object [20] [0]); }
nadre
1
Pourriez-vous s'il vous plaît lien vers cette réponse pour JUnit 5? Il décrit la fonctionnalité demandée qui a été ajoutée dans JUnit 5
Marcono1234
100

Avec IntelliJ, vous pouvez le faire à partir de la configuration de test. Une fois que vous ouvrez cette fenêtre, vous pouvez choisir d'exécuter le test autant de fois que vous le souhaitez.

entrez la description de l'image ici

lorsque vous exécutez le test, intellij exécutera tous les tests que vous avez sélectionnés pour le nombre de fois que vous avez spécifié.

Exemple exécutant 624 tests 10 fois: entrez la description de l'image ici

smac89
la source
4
C'est parfait, maintenant si vous pouvez indiquer une manière d'éclipse de faire cela, cela répondrait à la question du PO au point
khal
S'appuyer sur un outil spécifique pour héberger la logique ou les exigences réelles est un anti-modèle.
Mickael du
1
@Mickael Répéter un test N fois n'est généralement pas une exigence de test. En fait, les tests doivent être déterministes, de sorte que peu importe le nombre de fois qu'ils sont répétés, ils doivent toujours produire le même résultat. Pouvez-vous expliquer l'anti pattern dont vous parlez?
smac89
Si la répétition d'un test a été utile pour 1 développeur, cela sera probablement utile pour d'autres. Donc si le runtime de test et le code peuvent héberger la logique pour permettre la répétition, il doit être préféré car cela permet de factoriser l'effort et la solution et permettre aux contributeurs d'utiliser l'outil comme ils le souhaitent avec le même résultat. Mettre une logique réutilisable dans la zone IDE / développeur lorsqu'elle peut être placée dans le code est une sorte de factorisation manquante.
Mickael
68

J'ai trouvé que l'annotation de répétition de Spring est utile pour ce genre de chose:

@Repeat(value = 10)

Dernier document (API Spring Framework 4.3.11.RELEASE):

Laura
la source
46
Changer les cadres de test n'est pas ce que j'appellerais un moyen facile de le faire.
Stefan Thyberg le
3
Vous n'avez pas besoin de changer votre cadre de test - cela fonctionne bien avec JUnit. Le principal inconvénient est que JUnit le considère toujours comme un test unique. Donc, la première fois que ça casse, l'exécution s'arrête. Cependant, si vous n'utilisez pas déjà Spring, alors ce n'est probablement pas la voie que vous voulez faire ...
tveon
Cela ne semble pas fonctionner pour moi (Spring 4.3.6 via Spring Boot 1.5.1)
David Tonhofer
Ne fonctionne pas pour moi avec Spring Boot 2.1.6 et Junit 5
JO-
Fonctionne parfaitement avec spring boot 2. N'oubliez pas d'ajouter @RunWith (SpringRunner :: class), selon le lien «test unitaire au printemps» de l'affiche!
Agoston Horvath
33

Inspiré des ressources suivantes:

Exemple

Créez et utilisez une @Repeatannotation comme suit:

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'utilisation de cette solution avec @RunWith(PowerMockRunner.class), nécessite une mise à jour vers Powermock 1.6.5 (qui inclut un correctif ).

R. Oosterholt
la source
Oui. Comment exécutez-vous les tests?
R. Oosterholt
Je n'utilise pas Eclipse moi-même. Peut-être n'utilisez-vous pas un testeur Junut 4? (voir doc "Personnalisation d'une configuration de test" )
R. Oosterholt
29

Avec JUnit 5, j'ai pu résoudre ce problème en utilisant l' annotation @RepeatedTest :

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

Notez que l' @Testannotation ne doit pas être utilisée avec @RepeatedTest.

César Alberca
la source
Cela semble très prometteur, juste pour noter qu'il n'y a pas encore de version de sortie.
9ilsdx 9rvj 0lo
1
JUnit 5 est sorti il ​​y a quelques semaines.
mkobit
11

Quelque chose ne va pas avec:

@Test
void itWorks() {
    // stuff
}

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

Contrairement au cas où vous testez chacune d'un tableau de valeurs, vous ne vous souciez pas particulièrement de l'échec de l'exécution.

Pas besoin de faire en configuration ou en annotation ce que vous pouvez faire dans le code.

Soru
la source
2
Je voudrais exécuter plusieurs tests en tant que tests unitaires normaux et obtenir une trace et un statut pour chacun.
Stefan Thyberg
24
Dans ce cas, les s "@Before" et "@After" ne seront pas exécutés
Bogdan
3
Ceci avec l'appel manuel de la @Beforeméthode annotée avant de itWorks() résoudre mon problème.
João Neves
Connaissez-vous le concept DRY? fr.wikipedia.org/wiki/Don%27t_repeat_yourself Je recommande de faire une configuration au lieu de copier-coller votre boucle partout.
Kikiwa le
La file d'attente d'édition pour cette réponse est pleine; donc, je vais le mettre dans un commentaire: pour JUnit4, les tests doivent être publics.
Richard Jessop le
7

Cela fonctionne beaucoup plus facilement pour moi.

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
la source
Génial car n'utilise pas un autre framework et fonctionne réellement avec JUnit 3 (crucial pour Android)
Vladimir Ivanov
1
Une implémentation avec JUnit4 pourrait être faite avec 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); } } }Bien qu'au moins dans le plugin Eclipse JUnit, vous obtenez des résultats comme: "10/1 tests passés"
Peter Wippermann
7

Il y a une annotation intermittente dans la bibliothèque tempus-fugit qui fonctionne avec JUnit 4.7 @Rulepour répéter un test plusieurs fois ou avec @RunWith.

Par exemple,

@RunWith(IntermittentTestRunner.class)
public class IntermittentTestRunnerTest {

   private static int testCounter = 0;

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

Une fois le test exécuté (avec IntermittentTestRunner dans le @RunWith), testCounterserait égal à 99.

Toby
la source
Ouais, c'est le même problème ici, en utilisant déjà un autre coureur et donc je ne peux pas utiliser celui-ci, bonne idée cependant.
Stefan Thyberg
Oui, j'ai le même problème avec RunWith ... au fur et à mesure, j'ai modifié tempus-fugit pour le contourner un peu, vous pouvez utiliser un @Rule plutôt qu'un runner lorsque vous voulez courir à plusieurs reprises. Vous le marquez avec @Repeating au lieu de intermittent. La version de la règle ne fonctionnera cependant pas avec @ Before / @ Afters. Voir tempus-fugit.googlecode.com/svn/site/documentation/… ( faites défiler vers le bas pour charger / tremper les tests) pour plus de détails.
Toby
0

Je construis un module qui permet de faire ce genre de tests. Mais il ne se concentre pas uniquement sur la répétition. Mais pour garantir que certains morceaux de code sont sécurisés pour les threads.

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

Dépendance Maven:

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

Exemple d'utilisation:

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

C'est un projet open source. N'hésitez pas à vous améliorer.

Anderson Marques
la source
0

Vous pouvez exécuter votre test JUnit à partir d'une méthode principale et le répéter autant de fois que vous en avez besoin:

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
la source
0

C'est essentiellement la réponse que Yishai a fournie ci-dessus, réécrite 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() { }
}
marque
la source