2010-02-01 6 views
66

JUnit 4.8 contient une nouvelle fonctionnalité appelée "Catégories" qui vous permet de regrouper certains types de tests. Ceci est très utile, par ex. avoir des tests séparés pour les tests lents et rapides. Je connais les choses mentionnées dans JUnit 4.8 release notes, mais j'aimerais savoir comment je peux réellement exécuter tous les tests annotés avec certaines catégories.Comment exécuter tous les tests appartenant à une certaine catégorie dans JUnit 4

Les notes de version JUnit 4.8 montrent une définition de suite par exemple, où annotation SuiteClasses sélectionne les essais de certaines catégories à courir, 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 des SlowTests ? Il semble que vous devez avoir l'annotation SuiteClasses ...

+1

Hi. J'ai une question qui est liée. n'hésitez pas à entrer: http://stackoverflow.com/questions/15776718/using-junit-categories-vs-simply-organizing-logical-test-categories-in-separate – amphibient

Répondre

59

J'ai trouvé une façon possible d'obtenir 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éfinir la suite de tests pour les tests lents comme ceci:

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

AllTests classe est définie comme ceci:

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

je devais utiliser la classe ClassPathSuite de ClassPathSuite projet ici. Il trouvera toutes les classes avec des tests.

+4

C'est en fait une solution tout à fait raisonnable. Merci d'avoir répondu à votre propre question car elle est vraiment bonne :-) –

+0

Pour tous ceux qui se demandent comment automatiser l'exécution d'une catégorie de tests (avec cette configuration exacte) en utilisant Ant, [cette question] (http://stackoverflow.com/questions/6226026/how-to-run-all-junit-tests-in-a-category-suite-with-ant) pourrait être utile. – Jonik

+3

Comme un compliment à ma question http://stackoverflow.com/q/2698174/59470 et une explication détaillée, j'ai ajouté une entrée de blog: http://novyden.blogspot.com/2011/06/using-junit-4-categories -remplacer.html – topchef

1

Je ne suis pas sûr, quel est exactement votre problème.

Ajoutez simplement tous les tests à une suite (ou hirachy 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 se référant à la première, en spécifiant l'ensemble différent de catégories dont vous avez besoin.

+19

Mon problème est que j'ai des milliers de tests et Je ne veux pas les ajouter manuellement à une suite. Je veux juste que les tests avec certaines catégories soient exécutés. Il ne devrait pas être si difficile pour JUnit de savoir quels tests ont certaines annotations comme c'est le cas quand on trouve des méthodes de test. – Kaitsu

1

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

Pourquoi vos tests 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 en fait des tests d'intégration.

Dans mes configurations, j'ai env une mise en scène, où les tests unitaires sont exécutés souvent et les tests d'intégration constamment mais plus rarement (par exemple après chaque validation dans le contrôle de version). Je n'ai jamais travaillé avec le groupage pour les tests unitaires, car ils devraient être couplés lâchement tous ensemble. Je ne travaille qu'avec le groupement et la relation des cas de test dans les configurations d'intégration-test (mais avec TestNG).

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

+1

Merci Manuel pour vos commentaires! Je n'ai pas vraiment besoin de séparer les tests unitaires, mais j'utilise aussi JUnit pour les tests d'intégration et je veux les séparer des tests unitaires. J'ai aussi regardé TestNG et il semble rendre les tests (et pas seulement les tests unitaires) plus agréables que JUnit. Et il a aussi une meilleure documentation et un bon livre. – Kaitsu

7

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

  • JUnit de sont typés (annotations) tandis que celle des 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" database * ".De plus, devoir créer une nouvelle annotation chaque fois que vous devez créer une nouvelle catégorie est ennuyeux, bien qu'il ait l'avantage qu'un EDI 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 (les 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 les annotations et que vous devez spécifier ces catégories en tant que paramètres pour le coureur, 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 à mon encontre.

+0

Nous avons construit notre propre support de catégories dans nos tests JUnit d'une manière très similaire à JUnit, la différence principale étant qu'au lieu de l'annotation @ Categories.IncludeCategory, nous avons rendu le nôtre configurable via une propriété système. Pourquoi c'était trop dur pour JUnit de le faire pour nous? – Trejkaz

2

Pour exécuter des tests catégorisés sans spécifier tous les explicily dans @Suite.SuiteClasses annotation, vous pouvez fournir votre propre implémentation de la suite. Par exemple, un org.junit.runners.ParentRunner peut être étendu. Au lieu d'utiliser un tableau de classes fourni par @Suite.SuiteClasses, une nouvelle implémentation devrait effectuer une recherche de tests catégorisés dans classpath. Voir this project comme exemple d'une telle approche. Utilisation:

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

} 
5

Un inconvénient de la solution de Kaitsu est que Eclipse va exécuter vos tests deux fois, et les SlowTests 3 fois, lors de l'exécution de tous les tests dans un projet. En effet, l'Eclipse exécutera tous les tests, puis la suite AllTests, puis la suite SlowTestSuite.

Voici une solution qui consiste à créer des sous-classes de coureurs de test de la solution Kaitsu pour ignorer les suites, à moins qu'une certaine propriété système ne soit 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 lance-FastTestSuite, ajoutez simplement -Drun.dev.unit.tests = true aux arguments VM. (Notez que cette solution référence une suite de tests rapides au lieu d'une suite lente.)