web-dev-qa-db-fra.com

Ordre des classes de test junit

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?

10
Sándor Juhos

Permettez-moi de tout résumer avant de faire une recommandation.

  1. Les tests d'intégration sont lents. C'est bien et c'est naturel.
  2. La génération CI n'effectue pas de tests qui supposent le déploiement d'un système, car il n'y a pas de déploiement dans CI. Nous nous soucions du déploiement dans le processus CD. Je suppose donc que vos tests d'intégration ne supposent pas de déploiement.
  3. La construction CI exécute d'abord les tests unitaires. Les tests unitaires sont extrêmement rapides car ils utilisent uniquement de la RAM.
    Nous avons une bonne et rapide rétroaction des tests unitaires.

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:

  1. Améliorez les tests réels. Très souvent, ils ne sont pas efficaces et peuvent être considérablement accélérés.
  2. Exécutez des tests d'intégration en arrière-plan (c'est-à-dire n'attendez pas de commentaires en temps réel de leur part).
    Il est naturel qu'ils soient beaucoup plus lents que les tests unitaires.
  3. Répartissez les tests d'intégration sur les groupes et exécutez-les séparément si vous avez besoin de commentaires de certains d'entre eux plus rapidement.
  4. Exécutez des tests d'intégration dans différentes machines virtuelles Java. Pas de threads différents dans la même JVM!
    Dans ce cas, vous ne vous souciez pas de la sécurité des fils et vous ne devriez pas vous en soucier.
  5. Exécutez des tests d'intégration sur différentes machines, etc.

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.

4
nickolay.laptev

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,

  • car vous devez préparer ces TestSuites manuellement (ou écrire quelque chose qui les génère automatiquement)
  • et vous devez définir l'annotation simultanée pour toutes ces classes (peut-être avec un nombre différent de 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.

4
second

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.

4
Jackkobec

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.

0
agent_Karma