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.
JUnit 4.8包含一个名为“Categories”的新功能,允许您将某些类型的测试组合在一起。这非常有用,例如为慢速和快速测试进行单独的测试运行。我知道JUnit 4.8发行说明中提到的内容,但是想知道我如何实际运行所有使用某些类别注释的测试。
The JUnit 4.8 release notes show an example suite definition, where SuiteClasses annotation selects the tests from certain category to run, like this:
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
}
Does anyone know how I could run all the tests in SlowTests category? It seems that you must have the SuiteClasses annotation...
有谁知道如何在SlowTests类别中运行所有测试?您似乎必须拥有SuiteClasses注释...
6 个解决方案
#1
59
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.
我找到了一种可能的方法来实现我想要的,但我不认为这是最好的解决方案,因为它依赖于不属于JUnit的ClassPathSuite库。
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:
AllTests类定义如下:
@RunWith(ClasspathSuite.class)
public class AllTests {
}
I had to use ClassPathSuite class from ClassPathSuite project here. It will find all the classes with tests.
我不得不在这里使用ClassPathSuite项目中的ClassPathSuite类。它会找到所有带有测试的类。
#2
7
Here are some of the main differences between TestNG and JUnit when it comes to groups (or categories, like JUnit calls them):
以下是TestNG和JUnit在组(或类别,如JUnit调用它们)时的一些主要区别:
-
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).
JUnit是键入的(注释),而TestNG是字符串。我做出了这个选择,因为我希望能够在运行测试时使用正则表达式,例如“运行属于组的所有测试”数据库*“。此外,无论何时需要创建新的注释,都必须创建新的注释类别很烦人,虽然它有一个好处,IDE将立即告诉你这个类别的使用位置(TestNG在其报告中向您显示)。
-
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.
TestNG非常清楚地将运行模型(测试运行)中的静态模型(测试代码)分开。如果要先运行组“前端”然后再运行“servlets”,则无需重新编译任何内容即可。因为JUnit在注释中定义了组,并且您需要将这些类别指定为运行器的参数,所以每当您想要运行不同的类别集时,通常必须重新编译代码,这在我看来会失败。
#3
5
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.
Kaitsu解决方案的一个缺点是,在项目中运行所有测试时,Eclipse将运行两次测试,SlowTests运行3次。这是因为Eclipse将运行所有测试,然后运行AllTests套件,然后运行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.
这是一个解决方案,涉及创建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";
}
}
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.)
因此,在FastTestSuite启动器中,只需将-Drun.dev.unit.tests = true添加到VM参数即可。 (请注意,此解决方案引用了快速测试套件而不是慢速测试套件。)
#4
2
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.
要在@ Suite.SuiteClasses批注中明确指定所有这些分类测试,您可以提供自己的Suite实现。例如,可以扩展org.junit.runners.ParentRunner。新实现应该在类路径中执行搜索分类测试,而不是使用@ Suite.SuiteClasses提供的类数组。
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 {
}
#5
1
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.
只需将所有测试添加到套件(或套件的hirachy)。然后使用Categories Runner和Include / ExcludeCategory批注指定要运行的类别。
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.
一个好主意可能是让一个套件包含所有测试,并且有几个单独的套件引用第一个套件,指定您需要的不同类别。
#6
0
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.
为什么你的测试速度慢?也许设置持续时间很长(数据库,I / O等),也许测试测试太多了?如果是这种情况,我会从“长期运行”的单元测试中分离出真正的单元测试,这通常是集成测试。
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).
在我的设置中,我有staging env,其中经常运行单元测试并且不断地进行集成测试但是更少(例如在版本控制中的每次提交之后)。我从未使用过分组进行单元测试,因为它们应该完全松散耦合。我只在集成测试设置中使用测试用例的分组和关系(但是使用TestNG)。
But good to know that JUnit 4.8 introduced some grouping features.
但很高兴知道JUnit 4.8引入了一些分组功能。
#1
59
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.
我找到了一种可能的方法来实现我想要的,但我不认为这是最好的解决方案,因为它依赖于不属于JUnit的ClassPathSuite库。
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:
AllTests类定义如下:
@RunWith(ClasspathSuite.class)
public class AllTests {
}
I had to use ClassPathSuite class from ClassPathSuite project here. It will find all the classes with tests.
我不得不在这里使用ClassPathSuite项目中的ClassPathSuite类。它会找到所有带有测试的类。
#2
7
Here are some of the main differences between TestNG and JUnit when it comes to groups (or categories, like JUnit calls them):
以下是TestNG和JUnit在组(或类别,如JUnit调用它们)时的一些主要区别:
-
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).
JUnit是键入的(注释),而TestNG是字符串。我做出了这个选择,因为我希望能够在运行测试时使用正则表达式,例如“运行属于组的所有测试”数据库*“。此外,无论何时需要创建新的注释,都必须创建新的注释类别很烦人,虽然它有一个好处,IDE将立即告诉你这个类别的使用位置(TestNG在其报告中向您显示)。
-
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.
TestNG非常清楚地将运行模型(测试运行)中的静态模型(测试代码)分开。如果要先运行组“前端”然后再运行“servlets”,则无需重新编译任何内容即可。因为JUnit在注释中定义了组,并且您需要将这些类别指定为运行器的参数,所以每当您想要运行不同的类别集时,通常必须重新编译代码,这在我看来会失败。
#3
5
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.
Kaitsu解决方案的一个缺点是,在项目中运行所有测试时,Eclipse将运行两次测试,SlowTests运行3次。这是因为Eclipse将运行所有测试,然后运行AllTests套件,然后运行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.
这是一个解决方案,涉及创建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";
}
}
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.)
因此,在FastTestSuite启动器中,只需将-Drun.dev.unit.tests = true添加到VM参数即可。 (请注意,此解决方案引用了快速测试套件而不是慢速测试套件。)
#4
2
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.
要在@ Suite.SuiteClasses批注中明确指定所有这些分类测试,您可以提供自己的Suite实现。例如,可以扩展org.junit.runners.ParentRunner。新实现应该在类路径中执行搜索分类测试,而不是使用@ Suite.SuiteClasses提供的类数组。
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 {
}
#5
1
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.
只需将所有测试添加到套件(或套件的hirachy)。然后使用Categories Runner和Include / ExcludeCategory批注指定要运行的类别。
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.
一个好主意可能是让一个套件包含所有测试,并且有几个单独的套件引用第一个套件,指定您需要的不同类别。
#6
0
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.
为什么你的测试速度慢?也许设置持续时间很长(数据库,I / O等),也许测试测试太多了?如果是这种情况,我会从“长期运行”的单元测试中分离出真正的单元测试,这通常是集成测试。
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).
在我的设置中,我有staging env,其中经常运行单元测试并且不断地进行集成测试但是更少(例如在版本控制中的每次提交之后)。我从未使用过分组进行单元测试,因为它们应该完全松散耦合。我只在集成测试设置中使用测试用例的分组和关系(但是使用TestNG)。
But good to know that JUnit 4.8 introduced some grouping features.
但很高兴知道JUnit 4.8引入了一些分组功能。