【问题标题】:How to run all tests belonging to a certain Category in JUnit 4如何在 JUnit 4 中运行属于某个类别的所有测试
【发布时间】:2011-01-11 16:55:15
【问题描述】:

JUnit 4.8 包含一个名为“Categories”的不错的新功能,它允许您将某些类型的测试组合在一起。这非常有用,例如对慢速和快速测试进行单独的测试。我知道JUnit 4.8 release notes 中提到的内容,但我想知道如何实际运行所有带有特定类别注释的测试。

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

【问题讨论】:

标签: java unit-testing junit junit4


【解决方案1】:

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

只需将所有测试添加到一个套件(或多个套件)中。然后使用 Categories Runner 和 Include/ExcludeCategory 注解,指定您要运行的类别。

一个好主意可能是有一个包含所有测试的套件,以及引用第一个的几个单独的套件,指定您需要的不同类别集。

【讨论】:

  • 我的问题是我有数千个测试,我不想手动将它们添加到任何套件中。我只想运行特定类别的测试。 JUnit 应该不难找出哪些测试具有特定的注释,因为它在查找测试方法时实际上是这样做的。
【解决方案2】:

我找到了一种可能的方法来实现我想要的,但我不认为这是最好的解决方案,因为它依赖于不属于 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 类。它将找到所有带有测试的类。

【讨论】:

【解决方案3】:

不是直接回答您的问题,但也许可以改进一般方法...

为什么你的测试很慢?也许设置持续很长时间(数据库、I/O 等),也许测试测试太多了?如果是这种情况,我会将真正的单元测试与“长期运行”的单元测试分开,后者通常确实是集成测试。

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

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

【讨论】:

  • 感谢 Manuel 的 cmets!我真的不需要分离单元测试,但我也将 JUnit 用于集成测试,并希望将它们与单元测试分开。我也看过 TestNG,它似乎使测试(而不仅仅是单元测试)比 JUnit 更好。它还有更好的文档和一本好书。
【解决方案4】:

以下是 TestNG 和 JUnit 在组(或类别,如 JUnit 所称)方面的一些主要区别:

  • JUnit 是类型化的(注解),而 TestNG 是字符串。我做出这个选择是因为我希望能够在运行测试时使用正则表达式,例如“运行属于组“数据库*”的所有测试。此外,每当您需要创建一个新的注释时,都必须创建一个新的注释类别很烦人,尽管它的好处是 IDE 会立即告诉您该类别的使用位置(TestNG 在其报告中向您展示了这一点)。

  • TestNG 将您的静态模型(测试代码)与运行时模型(运行测试)非常清晰地分开。如果您想先运行组“前端”然后运行“servlet”,您可以这样做而无需重新编译任何东西。因为 JUnit 在注解中定义了组,并且您需要将这些类别指定为运行程序的参数,所以每当您想要运行一组不同的类别时,您通常都必须重新编译您的代码,这在我看来违背了目的。

【讨论】:

  • 我们以与 JUnit 非常相似的方式将自己的类别支持构建到 JUnit 测试中,主要区别在于我们通过系统属性而不是 @Categories.IncludeCategory 注释来配置我们的类别。为什么这对 JUnit 来说太难为我们做是任何人的猜测。
【解决方案5】:

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

请参阅this project 作为此类方法的示例。 用法:

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

}

【讨论】:

    【解决方案6】:

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

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

    @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 参数即可。 (请注意,此解决方案引用的是快速测试套件而不是慢速测试套件。)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-12-09
      • 1970-01-01
      • 2011-03-07
      • 1970-01-01
      • 2012-11-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多