web-dev-qa-db-fra.com

Tests JUnit simultanés

J'ai une grande suite de tests JUnit, où j'aimerais tout à fait exécuter tous les tests simultanément pour deux raisons:

  • Exploitez plusieurs cœurs pour exécuter toute la suite de tests plus rapidement
  • Espérons détecter certaines erreurs dues à des objets globaux non thread-safe

Je reconnais que cela me forcera à refactoriser du code pour le rendre thread-safe, mais je considère que c'est une bonne chose :-)

Quelle est la meilleure façon d'obtenir JUnit pour exécuter tous les tests simultanément?

68
mikera

Êtes-vous fixé sur JUnit? TestNG fournit de bons tests multi-threads prêts à l'emploi et il est compatible avec les tests JUnit (vous devez apporter quelques modifications). Par exemple, vous pouvez exécuter un test comme celui-ci:

@Test(threadPoolSize = 3, invocationCount = 9,  timeOut = 10000)
public void doSomething() {
...
}

Cela signifierait que la méthode doSomething() sera invoquée 9 fois par 3 threads différents.

Je recommande fortement TestNG.

31
Chandradeep

Je cherchais une réponse à exactement cette question, et sur la base des réponses ici et de ce que j'ai lu ailleurs, il semble qu'il n'y ait actuellement pas de méthode simple et prête à l'emploi pour exécuter des tests existants en parallèle en utilisant JUnit. Ou s'il y en a je ne l'ai pas trouvé. J'ai donc écrit un simple JUnit Runner qui accomplit cela. N'hésitez pas à l'utiliser; voir http://falutin.net/2012/12/30/multithreaded-testing-with-junit/ pour une explication complète et le code source de la classe MultiThreadedRunner. Avec cette classe, vous pouvez simplement annoter vos classes de test existantes comme ceci:

@RunWith(MultiThreadedRunner.class)
21
Mike Sokolov

Le code suivant devrait répondre à vos besoins, qui est tiré du livre allemand JUnit Profiwissen qui contient quelques conseils pour tester des trucs en parallèle ou sur la façon de réduire le temps d'exécution en utilisant plusieurs cœurs au lieu d'un seul cœur .

JUnit 4.6 a introduit une classe ParallelComputer qui offrait l'exécution parallèle de tests. Cependant, cette fonctionnalité n'était pas accessible au public avant JUnit 4.7 qui offrait la possibilité de définir un planificateur personnalisé pour le runner parent.

public class ParallelScheduler implements RunnerScheduler {

    private ExecutorService threadPool = Executors.newFixedThreadPool(
        Runtime.getRuntime().availableProcessors());

    @Override
    public void schedule(Runnable childStatement) {
        threadPool.submit(childStatement);
    }

    @Override
    public void finished() {
        try {
            threadPool.shutdown();
            threadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("Got interrupted", e);
        }
    }
}

public class ParallelRunner extends BlockJUnit4ClassRunner {

    public ParallelRunner(Class<?> klass) throws InitializationError {
        super(klass);
        setScheduler(new ParallelScheduler());
    }
}

Si vous annotez maintenant une classe de test avec @RunWith(ParallelRunner.class) chaque méthode s'exécutera dans son propre thread. De plus, il y aura autant de threads actifs (uniquement) que de cœurs CPU disponibles sur la machine d'exécution.

Si plusieurs classes doivent être exécutées en parallèle, vous pouvez définir une suite personnalisée comme celle-ci:

public class ParallelSuite extends Suite {

    public ParallelSuite(Class<?> klass, RunnerBuilder builder) 
      throws InitializationError {
        super(klass, builder);
        setScheduler(new ParallelScheduler());
    }
}

puis changez @RunWith(Suite.class) avec @RunWith(ParallelSuite.class)

Vous pouvez même tirer parti des fonctionnalités de f.e. WildcardPatternSuite en étendant directement à partir de cette suite au lieu de Suite comme dans l'exemple précédent. Cela vous permet en outre de filtrer les tests unitaires f.e. par tout @Category - une TestSuite qui n'exécute que UnitTest catégories annotées en parallèle pourrait ressembler à ceci:

public interface UnitTest {

}

@RunWith(ParallelSuite.class)
@SuiteClasses("**/*Test.class")
@IncludeCategories(UnitTest.class)
public class UnitTestSuite {

}

Un cas de test simple pourrait maintenant ressembler à ceci:

@Category(UnitTest.class)
@RunWith(MockitoJUnitRunner.class)
public class SomeClassTest {

    @Test
    public void testSomething() {
        ...
    }
}

UnitTestSuite exécutera chaque classe trouvée dans les sous-répertoires qui se termine par Test et a une @Category(UnitTest.class) spécifiée en parallèle - en fonction du nombre de cœurs CPU disponibles.

Je ne sais pas si cela peut devenir plus simple que ça :)

21
Roman Vottner

Apparemment Mathieu Carbou a fait une mise en œuvre pour concours qui pourrait aider!

http://Java.dzone.com/articles/concurrent-junit-tests

OneJunit

@RunWith(ConcurrentJunitRunner.class)
@Concurrent(threads = 6)
public final class ATest {

    @Test public void test0() throws Throwable { printAndWait(); }
    @Test public void test1() throws Throwable { printAndWait(); }
    @Test public void test2() throws Throwable { printAndWait(); }
    @Test public void test3() throws Throwable { printAndWait(); }
    @Test public void test4() throws Throwable { printAndWait(); }
    @Test public void test5() throws Throwable { printAndWait(); }
    @Test public void test6() throws Throwable { printAndWait(); }
    @Test public void test7() throws Throwable { printAndWait(); }
    @Test public void test8() throws Throwable { printAndWait(); }
    @Test public void test9() throws Throwable { printAndWait(); }

    void printAndWait() throws Throwable {
        int w = new Random().nextInt(1000);
        System.out.println(String.format("[%s] %s %s %s",Thread.currentThread().getName(), getClass().getName(), new Throwable       ().getStackTrace()[1].getMethodName(), w));
        Thread.sleep(w);
    }
}

Plusieurs unités J:

@RunWith(ConcurrentSuite.class)
@Suite.SuiteClasses({ATest.class, ATest2.class, ATest3.class})
public class MySuite {
}
6
molavec

Vous pouvez également essayer HavaRunner . Il s'agit d'un exécuteur JUnit qui exécute des tests en parallèle par défaut.

HavaRunner propose également des suites pratiques: vous pouvez déclarer un test comme membre d'une suite en ajoutant l'annotation @PartOf(YourIntegrationTestSuite.class) à la classe. Cette approche diffère de JUnit, où vous déclarez les appartenances à la suite dans la classe de suite.

De plus, les suites HavaRunner peuvent initialiser des objets lourds tels qu'un conteneur d'application Web intégré. HavaRunner transmet ensuite cet objet lourd au constructeur de chaque membre de la suite. Cela supprime le besoin de @BeforeClass et @AfterClass les annotations, qui sont problématiques, car elles favorisent un état global mutable, ce qui rend la parallélisation difficile.

Enfin, HavaRunner propose des scénarios - un moyen d'exécuter le même test avec des données différentes. Les scénarios réduisent la nécessité de dupliquer le code de test.

HavaRunner a été testé au combat dans deux projets de taille moyenne Java.

Ps. Je suis l'auteur de HavaRunner et j'apprécierais vos commentaires à ce sujet.

1
Lauri Lehmijoki