Comment exécuter tous les tests appartenant à une certaine catégorie dans JUnit 4

JUnit 4.8 contient une nouvelle fonctionnalité intéressante appelée “Catégories” qui vous permet de regrouper certains types de tests. Ceci est très utile, par exemple pour avoir des tests séparés pour des tests lents et rapides. Je connais les éléments mentionnés dans les notes de version de JUnit 4.8 , mais je voudrais savoir comment je peux exécuter tous les tests annotés avec certaines catégories.

Les notes de version de JUnit 4.8 montrent un exemple de définition de suite, dans laquelle l’annotation SuiteClasses sélectionne les tests à exécuter dans une catégorie donnée, comme ceci:

@RunWith(Categories.class) @IncludeCategory(SlowTests.class) @SuiteClasses( { A.class, B.class }) // Note that Categories is a kind of Suite public class SlowTestSuite { // Will run Ab and Bc, but not Aa } 

Est-ce que quelqu’un sait comment je pourrais exécuter tous les tests dans la catégorie SlowTests? Il semble que vous devez avoir l’annotation SuiteClasses …

J’ai découvert un moyen possible de réaliser ce que je voulais, mais je ne considère pas que ce soit la meilleure solution possible car elle repose sur la bibliothèque ClassPathSuite qui ne fait pas partie de JUnit.

Je définis la suite de tests pour des tests lents comme ceci:

 @RunWith(Categories.class) @Categories.IncludeCategory(SlowTests.class) @Suite.SuiteClasses( { AllTests.class }) public class SlowTestSuite { } 

La classe AllTests est définie comme suit:

 @RunWith(ClasspathSuite.class) public class AllTests { } 

J’ai dû utiliser la classe ClassPathSuite du projet ClassPathSuite ici. Il trouvera toutes les classes avec des tests.

Voici quelques-unes des principales différences entre TestNG et JUnit en ce qui concerne les groupes (ou les catégories, comme les appelle JUnit):

  • Les JUnit sont tapés (annotations) alors que les TestNG sont des chaînes. J’ai fait ce choix car je voulais pouvoir utiliser des expressions régulières lors de l’exécution de tests, par exemple “exécuter tous les tests appartenant au groupe” database * “. De plus, créer une nouvelle annotation chaque fois que vous devez créer un nouveau la catégorie est agaçante, bien qu’elle ait l’avantage qu’un IDE vous dira tout de suite où cette catégorie est utilisée (TestNG vous le montre dans ses rapports).

  • TestNG sépare très clairement votre modèle statique (le code de vos tests) du modèle d’exécution (quels tests sont exécutés). Si vous voulez exécuter les groupes “front-end” d’abord, puis “servlets”, vous pouvez le faire sans avoir à recomstackr quoi que ce soit. Étant donné que JUnit définit des groupes dans les annotations et que vous devez spécifier ces catégories en tant que parameters pour le runner, vous devez généralement recomstackr votre code chaque fois que vous souhaitez exécuter un ensemble de catégories différent, ce qui va à l’encontre de l’objective.

Un inconvénient de la solution de Kaitsu est qu’Eclipse exécutera vos tests deux fois et les SlowTests trois fois lors de l’exécution de tous les tests d’un projet. En effet, l’Eclipse exécutera tous les tests, puis la suite AllTests, puis SlowTestSuite.

Voici une solution qui implique la création de sous-classes des exécuteurs de test de la solution Kaitsu pour ignorer les suites, sauf si une propriété système donnée est définie. Un piratage honteux, mais tout ce que j’ai imaginé jusqu’à présent.

 @RunWith(DevFilterClasspathSuite.class) public class AllTests {} 

.

 @RunWith(DevFilterCategories.class) @ExcludeCategory(SlowTest.class) @SuiteClasses(AllTests.class) public class FastTestSuite { } 

.

 public class DevFilterCategories extends Suite { private static final Logger logger = Logger .getLogger(DevFilterCategories.class.getName()); public DevFilterCategories(Class suiteClass, RunnerBuilder builder) throws InitializationError { super(suiteClass, builder); try { filter(new CategoryFilter(getIncludedCategory(suiteClass), getExcludedCategory(suiteClass))); filter(new DevFilter()); } catch (NoTestsRemainException e) { logger.info("skipped all tests"); } assertNoCategorizedDescendentsOfUncategorizeableParents(getDescription()); } private Class getIncludedCategory(Class klass) { IncludeCategory annotation= klass.getAnnotation(IncludeCategory.class); return annotation == null ? null : annotation.value(); } private Class getExcludedCategory(Class klass) { ExcludeCategory annotation= klass.getAnnotation(ExcludeCategory.class); return annotation == null ? null : annotation.value(); } private void assertNoCategorizedDescendentsOfUncategorizeableParents(Description description) throws InitializationError { if (!canHaveCategorizedChildren(description)) assertNoDescendantsHaveCategoryAnnotations(description); for (Description each : description.getChildren()) assertNoCategorizedDescendentsOfUncategorizeableParents(each); } private void assertNoDescendantsHaveCategoryAnnotations(Description description) throws InitializationError { for (Description each : description.getChildren()) { if (each.getAnnotation(Category.class) != null) throw new InitializationError("Category annotations on Parameterized classes are not supported on individual methods."); assertNoDescendantsHaveCategoryAnnotations(each); } } // If children have names like [0], our current magical category code can't determine their // parentage. private static boolean canHaveCategorizedChildren(Description description) { for (Description each : description.getChildren()) if (each.getTestClass() == null) return false; return true; } } 

.

 public class DevFilterClasspathSuite extends ClasspathSuite { private static final Logger logger = Logger .getLogger(DevFilterClasspathSuite.class.getName()); public DevFilterClasspathSuite(Class suiteClass, RunnerBuilder builder) throws InitializationError { super(suiteClass, builder); try { filter(new DevFilter()); } catch (NoTestsRemainException e) { logger.info("skipped all tests"); } } } 

.

 public class DevFilter extends Filter { private static final Ssortingng RUN_DEV_UNIT_TESTS = "run.dev.unit.tests"; @Override public boolean shouldRun(Description description) { return Boolean.getBoolean(RUN_DEV_UNIT_TESTS); } @Override public Ssortingng describe() { return "filter if "+RUN_DEV_UNIT_TESTS+" system property not present"; } } 

Ainsi, dans votre lanceur FastTestSuite, ajoutez simplement -Drun.dev.unit.tests = true aux arguments de la machine virtuelle. (Notez que cette solution fait référence à une suite de tests rapide plutôt qu’à une suite lente.)

Pour exécuter des tests catégorisés sans tous les spécifier explicitement dans les annotations de @Suite.SuiteClasses vous pouvez fournir votre propre implémentation de Suite. Par exemple, un org.junit.runners.ParentRunner peut être étendu. Au lieu d’utiliser un tableau de classes fourni par @Suite.SuiteClasses , la nouvelle implémentation doit rechercher les tests catégorisés dans classpath.

Voir ce projet comme exemple d’une telle approche. Usage:

 @Categories(categoryClasses = {IntegrationTest.class, SlowTest.class}) @BasePackage(name = "some.package") @RunWith(CategorizedSuite.class) public class CategorizedSuiteWithSpecifiedPackage { } 

Je ne sais pas exactement quel est votre problème.

Ajoutez simplement tous les tests à une suite (ou à une suite de suites). Utilisez ensuite l’annotation Categories Runner et Include / ExcludeCategory pour spécifier les catégories à exécuter.

Une bonne idée pourrait être d’avoir une suite contenant tous les tests, et quelques suites distinctes faisant référence au premier, en spécifiant les différentes catégories de catégories.

Pas une réponse directe à votre problème, mais peut-être que l’approche générale pourrait être améliorée …

Pourquoi vos tests sont-ils lents? Peut-être que la configuration dure longtemps (firebase database, E / S, etc.), peut-être que les tests sont trop testés? Si tel est le cas, je séparerais les tests unitaires réels des tests de longue durée, qui sont souvent des tests d’intégration.

Dans mes configurations, j’ai un environnement de mise en scène, où les tests unitaires sont exécutés souvent et les tests d’intégration en permanence, mais plus rarement (par exemple, après chaque validation dans le contrôle de version). Je n’ai jamais travaillé avec le groupement pour les tests unitaires, car ils devraient être couplés de manière souple. Je travaille uniquement avec le regroupement et la relation des cas de test dans les configurations de test d’intégration (mais avec TestNG).

Mais bon de savoir que JUnit 4.8 a introduit des fonctionnalités de regroupement.