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

发布于 2024-08-20 00:45:23 字数 602 浏览 9 评论 0原文

JUnit 4.8 包含一个名为“类别”的不错的新功能,它允许您将某些类型的测试分组在一起。这非常有用,例如对于慢速和快速测试进行单独的测试运行。我知道 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 注释......

JUnit 4.8 contains a nice new feature called "Categories" that allows you to group certain kinds of tests together. This is very useful, e.g. to have separate test runs for slow and fast tests. I know the stuff mentioned in JUnit 4.8 release notes, but would like to know how I can actually run all the tests annotated with certain category.

The JUnit 4.8 release notes show an example suite definition, where SuiteClasses annotation selects the tests from certain category to run, like this:

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

Does anyone know how I could run all the tests in SlowTests category? It seems that you must have the SuiteClasses annotation...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

人海汹涌 2024-08-27 00:45:23

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

I found out one possible way to achieve what I want, but I don't consider this to be the best possible solution as it relies on ClassPathSuite library that is not part of JUnit.

I define the test suite for slow tests like this:

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

AllTests class is defined like this:

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

I had to use ClassPathSuite class from ClassPathSuite project here. It will find all the classes with tests.

-柠檬树下少年和吉他 2024-08-27 00:45:23

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

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

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

Here are some of the main differences between TestNG and JUnit when it comes to groups (or categories, like JUnit calls them):

  • JUnit's are typed (annotations) while TestNG's are strings. I made this choice because I wanted to be able to use regular expressions when running tests, for example "run all the tests that belong to the group "database*". Also, having to create a new annotation whenever you need to create a new category is annoying, although it has the benefit that an IDE will tell you right away where this category is used (TestNG shows you this in its reports).

  • TestNG separates very clearly your static model (the code of your tests) from the runtime model (which tests get run). If you want to run the groups "front-end" first and then "servlets", you can do this without having to recompile anything. Because JUnit defines groups in annotations and you need to specify these categories as parameters to the runner, you usually have to recompile your code whenever you want to run a different set of categories, which defeats the purpose in my opinion.

_失温 2024-08-27 00:45:23

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

One downside to Kaitsu's solution is that Eclipse will run your tests twice, and the SlowTests 3 times, when running all the tests in a project. This is because the Eclipse will run all the tests, then the AllTests suite, then the SlowTestSuite.

Here is a solution that involves creating subclasses of the Kaitsu solution test runners to skip the suites unless a certain system property is set. A shameful hack, but all I have come up with so far.

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

So, in your FastTestSuite launcher, just add -Drun.dev.unit.tests=true to the VM arguments. (Note that this solution references a fast test suite instead of a slow one.)

萌︼了一个春 2024-08-27 00:45:23

要运行分类测试而不在 @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 {

}

To run categorized tests without specifying all of them explicily in @Suite.SuiteClasses annotation you can provide your own implementation of Suite.
For example a org.junit.runners.ParentRunner can be extended. Instead of using an array of classes provided by @Suite.SuiteClasses, new implementation should perform search for categorized tests in classpath.

See this project as an example of such approach.
Usage:

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

}
南城追梦 2024-08-27 00:45:23

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

只需将所有测试添加到一个套件(或多个套件)中即可。然后使用“类别运行程序”和“包含/排除类别”注释来指定要运行的类别。

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

I am not sure, what exactly your problem is.

Just add all the tests to a suite (or hirachy of suites). Then use the Categories Runner and Include/ExcludeCategory annotation, to specify the categories you want to run.

A good idea might be to have one suite containing all the tests, and a couple of seperate suites referring to the first one, specifying the different set of Categories you neeed.

伏妖词 2024-08-27 00:45:23

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

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

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

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

Not a direct answer to your problem, but maybe the general approach could be improved...

Why are your tests slow? Maybe the set-up lasts long (database, I/O etc.), maybe the tests are testing too much? If this is the case I would seperate the real unit-tests from the "long-running" ones, which often indeed are integration tests.

In my setups I have staging env, where unit-tests are run often and integration-tests constantly but more rarely (e.g. after each commit in version control). I have never worked with grouping for unit tests, because they should be loosely coupled alltogether. I only work with grouping and relationship of test-cases in integration-test setups (but with TestNG).

But good to know that JUnit 4.8 introduced some grouping features.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文