如何在JUnit 4中运行属于某个特定类别的所有测试

73

JUnit 4.8 新增了一个很棒的功能叫做 "分类"(Categories),它允许您将某些类型的测试分组在一起。这非常有用,例如可以为慢速和快速测试运行不同的测试套件。我已经阅读了JUnit 4.8 发布说明中提到的内容,但是我想知道如何实际运行所有使用特定分类注释的测试。

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
嗨。我有一个相关的问题。请随意发表意见:https://dev59.com/wHDYa4cB1Zd3GeqPAXF0 - amphibient
虽然这不是直接相关的,但这是一种计算“按类别测试”的计数器的方法。 - Bsquare ℬℬ
6个回答

65

我发现了一种可能实现我想要的方式,但我不认为这是最好的解决方案,因为它依赖于不是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
这实际上是一个相当合理的解决方案。谢谢你回答自己的问题,因为这是一个非常好的问题 :-) - Konrad 'ktoso' Malawski
对于任何想知道如何使用Ant自动运行一类测试(具有此确切设置)的人,这个问题可能会很有用。 - Jonik
3
作为对我的问题https://dev59.com/qXE85IYBdhLWcg3wmEtW的赞扬和详细解释,我添加了一篇博客文章:http://novyden.blogspot.com/2011/06/using-junit-4-categories-to-replace.html。 - topchef
虽然这不是直接相关的,但这是一种计算“按类别测试”的计数器的方法。 - Bsquare ℬℬ

7
以下是 TestNG 和 JUnit 在分组方面的主要区别(或类别,如 JUnit 所称):
- JUnit 是通过类型化注释进行分组,而 TestNG 是通过字符串进行分组。我之所以做出这个选择,是因为我希望能够在运行测试时使用正则表达式,例如“运行所有属于组'数据库*'的测试”。而且,每次需要创建新类别时都必须创建一个新注释是很烦人的,但好处是 IDE 将立即告诉您此类别在哪里使用(TestNG 在其报告中显示此信息)。 - TestNG 非常清楚地将您的静态模型(测试代码)与运行时模型(哪些测试将运行)分开。如果您想先运行“前端”组,然后再运行“servlets”组,则可以在不必重新编译任何内容的情况下执行此操作。因为 JUnit 在注释中定义组,并且您需要将这些类别指定为参数传递给运行器,所以每当您想运行不同的类别集时,通常必须重新编译代码,这在我看来有违目的。

我们在 JUnit 测试中构建了自己的类别支持,方式与 JUnit 非常相似,主要区别在于,我们使用系统属性来配置它,而不是 @Categories.IncludeCategory 注释。为什么 JUnit 无法为我们完成这个任务,这是任何人都无法解释的。 - Hakanai

6
Kaitsu的解决方案的一个缺点是,在运行项目中的所有测试时,Eclipse会运行您的测试两次,SlowTests会运行三次。这是因为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参数中。(请注意,此解决方案引用了快速测试套件而不是缓慢的测试套件。)

2
为了在不在@Suite.SuiteClasses注释中显式指定所有分类测试的情况下运行它们,您可以提供自己的Suite实现。例如,可以扩展org.junit.runners.ParentRunner。新实现应该在类路径中搜索分类测试,而不是使用@Suite.SuiteClasses提供的类数组。
请参见此项目,以了解这种方法的示例。用法:
@Categories(categoryClasses = {IntegrationTest.class, SlowTest.class})
@BasePackage(name = "some.package")
@RunWith(CategorizedSuite.class)
public class CategorizedSuiteWithSpecifiedPackage {

}

1

我不确定您的问题具体是什么。

只需将所有测试添加到一个套件(或套件层次结构)中。然后使用"Categories Runner"和"Inlude/ExcludeCategory"注释来指定您要运行的类别。

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


20
我的问题是我有成千上万个测试,不想手动将它们添加到任何测试套件中。我只想运行带有特定类别的测试。JUnit 应该能够轻松找出哪些测试具有特定注释,因为查找测试方法时实际上已经这样做了。 - Kaitsu

0

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

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

在我的设置中,我有一个分阶段环境,在那里经常运行单元测试和集成测试,但更少地运行(例如,在版本控制中每次提交后)。我从未使用过单元测试分组,因为它们应该松散耦合在一起。我只在集成测试设置中使用测试用例的分组和关系(但使用TestNG)。

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


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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接