J'ai une application Java avec maven. Junit pour les tests, avec des plug-ins à sécurité intégrée et sûrs. J'ai plus de 2000 tests d'intégration. Pour accélérer le test, j'utilise jvmfork à sécurité intégrée pour exécuter mes tests. parallèle. J'ai une classe de test lourde, et ils s'exécutent généralement à la fin de mon exécution de test et cela ralentit mon processus de vérification CI. Le fichier runorder sûr: équilibré serait une bonne option pour moi, mais je ne peux pas l'utiliser parce que jvmfork . Pour renommer les classes de test ou passer à un autre package et l'exécuter par ordre alphabétique n'est pas une option. Une suggestion comment puis-je exécuter mes classes de test lent au début du processus de vérification?
Permettez-moi de tout résumer avant de faire une recommandation.
En ce moment, nous sommes sûrs que nous n'avons aucun problème à obtenir un retour rapide. Mais nous voulons toujours exécuter les tests d'intégration plus rapidement. Je recommanderais les solutions suivantes:
J'ai travaillé avec de nombreux projets différents (certains d'entre eux avaient une construction CI en cours d'exécution pendant 48 heures) et les 3 premières étapes étaient suffisantes (même pour les cas fous). L'étape # 4 est rarement nécessaire pour avoir de bons tests. L'étape # 5 concerne des situations très spécifiques.
Vous voyez que ma recommandation concerne le processus et non l'outil, car le problème est dans le processus.
Très souvent, les gens ignorent la cause première et essaient de régler l'outil (Maven dans ce cas). Ils obtiennent des améliorations cosmétiques mais avec un coût de maintenance élevé de la solution créée.
J'ai donné la combinaison de réponses, j'ai trouvé un essai:
La deuxième réponse est basée sur ces classes
de ce projet github
, qui est disponible sous la licence BSD-2.
J'ai défini quelques classes de test:
public class LongRunningTest {
@Test
public void test() {
System.out.println(Thread.currentThread().getName() + ":\tlong test - started");
long time = System.currentTimeMillis();
do {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
} while(System.currentTimeMillis() - time < 1000);
System.out.println(Thread.currentThread().getName() + ":\tlong test - done");
}
}
@Concurrent
public class FastRunningTest1 {
@Test
public void test1() {
try {
Thread.sleep(250);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + ":\tfrt1-test1 - done");
}
// +7 more repetions of the same method
}
J'ai ensuite défini les suites de tests:
(FastRunningTest2 est une copie de la première classe avec une sortie ajustée)
@SuiteClasses({LongRunningTest.class, LongRunningTest.class})
@RunWith(Suite.class)
public class SuiteOne {}
@SuiteClasses({FastRunningTest1.class, FastRunningTest2.class})
@RunWith(Suite.class)
public class SuiteTwo {}
@SuiteClasses({SuiteOne.class, SuiteTwo.class})
@RunWith(ConcurrentSuite.class)
public class TopLevelSuite {}
Lorsque j'exécute le TopLevelSuite
j'obtiens la sortie suivante:
TopLevelSuite-1-thread-1: long test - started FastRunningTest1-1-thread-4: frt1-test4 - done FastRunningTest1-1-thread-2: frt1-test2 - done FastRunningTest1-1-thread-1: frt1-test1 - done FastRunningTest1-1-thread-3: frt1-test3 - done FastRunningTest1-1-thread-5: frt1-test5 - done FastRunningTest1-1-thread-3: frt1-test6 - done FastRunningTest1-1-thread-1: frt1-test8 - done FastRunningTest1-1-thread-5: frt1-test7 - done FastRunningTest2-2-thread-1: frt2-test1 - done FastRunningTest2-2-thread-2: frt2-test2 - done FastRunningTest2-2-thread-5: frt2-test5 - done FastRunningTest2-2-thread-3: frt2-test3 - done FastRunningTest2-2-thread-4: frt2-test4 - done TopLevelSuite-1-thread-1: long test - done TopLevelSuite-1-thread-1: long test - started FastRunningTest2-2-thread-5: frt2-test8 - done FastRunningTest2-2-thread-2: frt2-test6 - done FastRunningTest2-2-thread-1: frt2-test7 - done TopLevelSuite-1-thread-1: long test - done
Ce qui montre essentiellement que le LongRunningTest
est exécuté en parallèle au FastRunningTests
. La valeur par défaut des threads utilisés pour l'exécution parallèle définie par l'annotation Concurrent
est 5
, qui peut être vu dans la sortie de l'exécution parallèle de FastRunningTests
.
L'inconvénient est que ces Threads
ne sont pas partagées entre FastRunningTest1
et FastRunningTest2
.
Ce comportement montre qu'il est "quelque peu" possible de faire ce que vous voulez faire (donc si cela fonctionne avec votre configuration actuelle est une question différente).
Je ne sais pas non plus si cela en vaut vraiment la peine,
TestSuites
manuellement (ou écrire quelque chose qui les génère automatiquement)threads
pour chaque classe)Comme cela montre essentiellement qu'il est possible de définir l'ordre d'exécution des classes et de déclencher leur exécution parallèle, il devrait également être possible d'obtenir que l'ensemble du processus n'utilise qu'un seul ThreadPool
(mais je ne suis pas sûr de ce que l'implication ce serait).
Comme tout le concept est basé sur un ThreadPoolExecutor, en utilisant un PriorityBlockingQueue
qui donne aux tâches de longue durée une priorité plus élevée, vous vous rapprochez de votre résultat idéal d'exécuter les tests de longue durée en premier.
J'ai expérimenté un peu plus et mis en œuvre mon propre runner de suite personnalisé et runner junit. L'idée derrière est que votre JUnitRunner soumette les tests dans une file d'attente qui est gérée par un seul ThreadPoolExecutor
. Parce que je n'ai pas implémenté d'opération de blocage dans le RunnerScheduler#finish
, je me suis retrouvé avec une solution où les tests de toutes les classes étaient passés à la file d'attente avant même que l'exécution ne commence. (Cela pourrait sembler différent s'il y avait plus de classes de test et de méthodes impliquées).
Au moins, cela prouve que vous pouvez jouer avec junit à ce niveau si vous le voulez vraiment.
Le code de mon poc est un peu compliqué et trop long à mettre ici, mais si quelqu'un est intéressé, je peux le pousser dans un projet github.
Dans notre projet, nous avions créé quelques interfaces de marqueurs (exemple
public interface SlowTestsCategory {}
)
et le mettre dans l'annotation @Category de JUnit dans la classe de test avec des tests lents.
@Category(SlowTestsCategory.class)
Après cela, nous avons créé des tâches spéciales pour Gradle pour exécuter des tests par catégorie ou quelques catégories par commande personnalisée:
task unitTest(type: Test) {
description = 'description.'
group = 'groupName'
useJUnit {
includeCategories 'package.SlowTestsCategory'
excludeCategories 'package.ExcludedCategory'
}
}
Cette solution est proposée par Gradle, mais peut-être qu'elle vous sera utile.
Vous pouvez utiliser des annotations dans Junit 5 pour définir l'ordre de test que vous souhaitez utiliser:
Depuis le guide de l'utilisateur de Junit 5: https://junit.org/junit5/docs/current/user-guide/#writing-tests-test-execution-order
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
@TestMethodOrder(OrderAnnotation.class)
class OrderedTestsDemo {
@Test
@Order(1)
void nullValues() {
// perform assertions against null values
}
@Test
@Order(2)
void emptyValues() {
// perform assertions against empty values
}
@Test
@Order(3)
void validValues() {
// perform assertions against valid values
}
}
La mise à niveau vers Junit5 peut être effectuée assez facilement et la documentation sur le lien au début de l'article contient toutes les informations dont vous pourriez avoir besoin.