web-dev-qa-db-fra.com

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 d'avoir des tests séparés pour des tests lents et rapides. Je connais les choses mentionnées dans Notes de version JUnit 4.8 , mais j'aimerais savoir comment je peux réellement exécuter tous les tests annotés avec une certaine catégorie.

Les notes de publication de JUnit 4.8 présentent un exemple de définition de suite, où l'annotation SuiteClasses sélectionne les tests d'une certaine catégorie à exécuter, 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 A.b and B.c, but not A.a
}

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 ...

70
Kaitsu

J'ai trouvé un moyen possible d'atteindre ce que je veux, 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 les tests lents comme celui-ci:

@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.

60
Kaitsu

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

  • Les JUnit sont typés (annotations) tandis que les TestNG sont des chaînes. J'ai fait ce choix parce que je voulais pouvoir utiliser des expressions régulières lors de l'exécution de tests, par exemple "exécuter tous les tests qui appartiennent au groupe" base de données * ". Aussi, avoir à créer une nouvelle annotation chaque fois que vous avez besoin de créer une nouvelle est ennuyeuse, bien qu'elle présente l'avantage qu'un IDE vous indiquera immédiatement 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 d'abord exécuter les groupes "front-end" puis "servlets", vous pouvez le faire sans avoir à recompiler quoi que ce soit. Étant donné que JUnit définit des groupes dans des annotations et que vous devez spécifier ces catégories en tant que paramètres au runner, vous devez généralement recompiler votre code chaque fois que vous souhaitez exécuter un ensemble de catégories différent, ce qui va à l'encontre de l'objectif à mon avis.

7
Cedric Beust

Un inconvénient de la solution de Kaitsu est que Eclipse exécutera vos tests deux fois et les tests lents 3 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 la SlowTestSuite.

Voici une solution qui consiste à créer des sous-classes des exécuteurs de test de solution Kaitsu pour ignorer les suites, sauf si une certaine propriété système est définie. Un hack honteux, mais tout ce que j'ai trouvé 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 String RUN_DEV_UNIT_TESTS = "run.dev.unit.tests";

    @Override
    public boolean shouldRun(Description description)
    {
        return Boolean.getBoolean(RUN_DEV_UNIT_TESTS);
    }

    @Override
    public String describe()
    {
        return "filter if "+RUN_DEV_UNIT_TESTS+" system property not present";
    }
}

Donc, dans votre lanceur FastTestSuite, ajoutez simplement -Drun.dev.unit.tests = true aux arguments VM. (Notez que cette solution fait référence à une suite de tests rapide au lieu d'une suite lente).)

5
Kevin Wong

Pour exécuter des tests catégorisés sans les spécifier tous explicitement dans @Suite.SuiteClasses annotation, 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 devrait effectuer une recherche de 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 {

}
2
Stanislau Fink

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

Ajoutez simplement tous les tests à une suite (ou un ensemble de suites). Utilisez ensuite l'annotation Catégories Runner et Inclure/ExcludeCategory pour spécifier les catégories que vous souhaitez exécuter.

Une bonne idée pourrait être d'avoir une suite contenant tous les tests, et quelques suites séparées faisant référence au premier, spécifiant les différents ensembles de catégories dont vous avez besoin.

1
Jens Schauder

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 (base de données, E/S, etc.), peut-être que les tests testent trop? Si tel est le cas, je séparerais les vrais tests unitaires des tests "de longue durée", qui sont souvent des tests d'intégration.

Dans mes configurations, j'ai staging env, 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 regroupement pour les tests unitaires, car ils devraient être lâchement couplés tous ensemble. 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 certaines fonctionnalités de regroupement.

0
manuel aldana