2010-02-01 47 views
66

中运行属于某个类别的所有测试JUnit 4.8包含一个很好的新功能,称为“类别”,允许您将某些类型的测试分组在一起。这是非常有用的,例如对慢速和快速测试进行单独的测试运行。我知道JUnit 4.8 release notes中提到的东西,但想知道如何实际运行所有使用特定类别注释的测试。如何在JUnit 4

JUnit的4.8发行说明中显示的例子套房定义,其中SuiteClasses注释选择从某些类别的测试运行,像这样:

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

有谁知道我怎么能运行在SlowTests类别中的所有测试?看起来你必须有SuiteClasses注释...

+1

嗨。我有一个相关的问题。随时chime:http://stackoverflow.com/questions/15776718/using-junit-categories-vs-simply-organizing-logical-test-categories-in-separate – amphibient

回答

59

我发现了一种可能的方式来实现我想要的,但我不认为这是最好的解决方案,因为它依赖于不属于JUnit的ClassPathSuite库。

我定义的测试套件慢测​​试是这样的:

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

AllTests中类是这样定义的:

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

我不得不在这里使用ClassPathSuite类从ClassPathSuite项目。它会找到所有的测试类。

+4

这实际上是一个相当合理的溶剂。感谢您对自己的问题的微笑,因为它是一个非常好的问题:-) –

+0

对于任何人想知道如何使用Ant自动运行一个测试类别(使用这个确切的设置),[这个问题](http://stackoverflow.com/questions/6226026/how-to-run-all-junit-tests-in-a-class-suite-with-ant)可能会有用。 – Jonik

+3

作为对我的问题http://stackoverflow.com/q/2698174/59470的恭维和详细的解释,我添加了一个博客条目:http://novyden.blogspot.com/2011/06/using-junit-4-categories -取代。html – topchef

1

我不确定,你的问题到底是什么。

只需将所有测试添加到套件(或多套套件)即可。然后使用Categories Runner和Include/ExcludeCategory注释来指定要运行的类别。

一个好主意可能是拥有一个包含所有测试的套件,以及一些指向第一个套件的独立套件,指定您需要的不同类别集合。

+19

我的问题是,我有成千上万的测试和我不想手动将它们添加到任何套件。我只想运行某个类别的测试。对于JUnit而言,找出哪些测试具有某些注释并不难,因为无论如何,找到测试方法时都会这样做。 – Kaitsu

1

没有直接回答你的问题,但也许一般的方法可以改善......

为什么你的测试慢?也许设置持续很久(数据库,I/O等),也许测试测试太多?如果是这样的话,我会从“长期运行”的单元测试中分离出真正的单元测试,这通常是集成测试。

在我的设置中,我有分段env,其中单元测试经常运行,集成测试不断,但更为罕见(例如每次在版本控制中提交后)。我从来没有与单元测试分组合作过,因为它们应该松散耦合在一起。我只能在集成测试设置中使用测试用例的分组和关系(但使用TestNG)。

但很高兴知道JUnit 4.8引入了一些分组功能。

+1

感谢Manuel的评论!我并不需要单独进行单元测试,但是我也使用JUnit进行集成测试,并希望将它们与单元测试分开。我也看过TestNG,它似乎使测试(而不仅仅是单元测试)比JUnit更好。它也有更好的文档和一本好书。 – Kaitsu

7

以下是一些TestNG的和JUnit之间的主要区别,当涉及到组(或类,JUnit的称他们):

  • JUnit的是有类型的(注释),而TestNG的是字符串。我做了这个选择是因为我想在运行测试时能够使用正则表达式,例如“运行属于该组的所有测试”database *“。此外,无论何时需要创建新类别,都必须创建一个新注释,尽管它具有IDE将立即告诉您使用此类别的好处的好处(TestNG在其报告中向您显示了这一点)。

  • TestNG非常清楚地将您的静态模型(您的测试代码)与运行时模型(哪些测试运行)分开。如果你想先运行组“前端”,然后再运行“servlets”,你可以做到这一点,而无需重新编译任何东西。由于JUnit在注释中定义了组,并且您需要将这些类别指定为运行器的参数,所以当您想运行不同的类别时,通常必须重新编译代码,这违背了我的看法。

+0

我们以与JUnit非常类似的方式将我们自己的类别支持构建到JUnit测试中,主要区别在于不是通过@ Categories.IncludeCategory注释,而是通过系统属性对其进行配置。为什么JUnit对我们来说太难了,这是任何人的猜测。 – Trejkaz

2

要没有@Suite.SuiteClasses注释explicily指定所有的人,你可以提供自己的套件的运行分类测试。 例如可以扩展org.junit.runners.ParentRunner。新的实现不应该使用@Suite.SuiteClasses提供的类的数组,而应该在类路径中搜索分类的测试。

请参阅this project作为这种方法的一个例子。 用法:

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

} 
5

一个缺点佳逸的解决方案是,将Eclipse运行测试两次,SlowTests 3次,运行项目中的所有测试时。这是因为Eclipse将运行所有测试,然后运行AllTests套件,然后运行SlowTestSuite。

这是一个解决方案,它涉及创建Kaitsu解决方案测试运行器的子类以跳过套件,除非设置了某个系统属性。可耻的黑客,但我迄今为止都想出了。

@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"; 
    } 
} 

因此,在你FastTestSuite发射器,只需添加-Drun.dev.unit.tests = true添加到VM参数。 (请注意,这个解决方案引用了一个快速测试套件,而不是一个慢速测试套件。)