如何构建可配置的 JUnit4 测试套件?
How can I build a configurable JUnit4 test suite?
Guava 对用 JUnit3 编写的集合实现进行了广泛的测试,如下所示:
/*
* Copyright (C) 2008 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
public class CollectionRemoveTester<E> extends AbstractTester<E> {
@CollectionFeature.Require(SUPPORTS_REMOVE)
@CollectionSize.Require(absent = ZERO)
public void testRemove_present() {
...
}
}
然后使用 TestSuiteBuilder
来测试不同的集合,这些集合传入一组特征和集合类型的生成器,并且一个高度反射的框架将测试方法集识别为 运行 .
我想在 JUnit4 中构建类似的东西,但我不清楚如何去做:构建我自己的 Runner
?理论?到目前为止我最好的猜测是写类似
的东西
abstract class AbstractCollectionTest<E> {
abstract Collection<E> create(E... elements);
abstract Set<Feature> features();
@Test
public void removePresentValue() {
Assume.assumeTrue(features().contains(SUPPORTS_REMOVE));
...
}
}
@RunWith(JUnit4.class)
class MyListImplTest<E> extends AbstractCollectionTest<E> {
// fill in abstract methods
}
一般问题类似于:在 JUnit4 中,我如何为接口类型构建一套测试,然后将这些测试应用于各个实现?
您可以使用 JUnit 规则有条件地忽略测试(它们最终会在 maven 报告中被跳过,但可能有一种方法 fiddle 解决这个我不知道的问题)。
这是基于规则 in this article. I changed the rule a bit, see here。
public abstract class AbstractCollectionTest {
@Rule
public ConditionalSupportRule rule = new ConditionalSupportRule();
private Collection<String> collection;
private Set<Class<? extends Feature>> features;
public AbstractCollectionTest(Collection<String> collection,
Class<? extends Feature> ... features) {
this.collection = collection;
this.features = new HashSet<>();
for (Class<? extends Feature> feature : features) {
this.features.add(feature);
}
}
@Test
@ConditionalSupport(condition = SupportsRemove.class)
public void remove() throws Exception {
// ...
System.out.println("test run");
}
private interface Feature {}
public class SupportsRemove implements RunCondition, Feature {
public SupportsRemove() {
}
@Override
public boolean isSatisfied() {
return features.contains(SupportsRemove.class);
}
}
数组列表的示例测试:
public class ArrayListTest extends AbstractCollectionTest {
public ArrayListTest() {
super(new ArrayList<>(), SupportsRemove.class);
}
}
一些不支持删除的列表:
public class UnmodifiableListTest extends AbstractCollectionTest {
public UnmodifiableListTest() {
super(Collections.unmodifiableList(new ArrayList<>()));
}
}
在 Junit 中,您可以使用 categories。例如,该套件将从注释为集成的 AllTestSuite 执行所有测试:
import org.junit.experimental.categories.Categories;
import org.junit.experimental.categories.Categories.IncludeCategory;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Categories.class)
@IncludeCategory(Integration.class)
@Suite.SuiteClasses ({AllTestsSuite.class} )
public class IntegrationTestSuite {}
您也可以使用@ExcludeCategory。这对于删除慢速测试很有用。类别 类 只是普通的旧 Java 类 或接口。例如:
public interface Integration{}
public interface Performance{}
public interface Slow{}
public interface Database{}
您只需相应地注释您的测试:
@Category(Integration.class)
public class MyTest{
@Test
public void myTest__expectedResults(){
[...]
一个测试可能有多个这样的类别:
@Category({Integration.class,Database.class})
public class MyDAOTest{
为简单起见,我通常使用 google 工具箱在测试文件夹中创建一个包含所有 类 的套件:
import org.junit.runner.RunWith;
import com.googlecode.junittoolbox.ParallelSuite;
import com.googlecode.junittoolbox.SuiteClasses;
@RunWith(ParallelSuite.class)
@SuiteClasses({"**/**.class", //All classes
enter code here "!**/**Suite.class" }) //Excepts suites
public class AllTestsSuite {}
这包括在 AllTestSuite 中的所有 类 相同的文件夹和子文件夹中,即使它们没有 _Test 后缀。但是将无法看到不在同一文件夹或子文件夹中的测试。 junit-toolbox 在 Maven 中可用:
<dependency>
<groupId>com.googlecode.junit-toolbox</groupId>
<artifactId>junit-toolbox</artifactId>
<version>2.2</version>
</dependency>
现在您只需执行适合您需要的套件:)
UPDATE:在Spring中有@IfProfileValue注释允许你有条件地执行测试,如:
@IfProfileValue(name="test-groups", values={"unit-tests", "integration-tests"})
@Test
public void testProcessWhichRunsForUnitOrIntegrationTestGroups() {
有关详细信息,请参阅 Spring JUnit Testing Annotations
关于是否构建您自己的 Runner...我认为您不应该立即尝试构建您自己的 Runner,而是参数化您的单元测试。
一个选项是用 @RunWith(Parameterized.class)
注释您的 class 并插入一个用 @Parameters
注释的静态方法,该方法将用于使用 JUnit 测试的构造函数进行实际参数化。下面是我无耻地取自 https://github.com/junit-team/junit/wiki/Parameterized-tests:
的例子
@RunWith(Parameterized.class)
public class FibonacciTest {
@Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] {
{ 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 }
});
}
private int fInput;
private int fExpected;
public FibonacciTest(int input, int expected) {
fInput= input;
fExpected= expected;
}
@Test
public void test() {
assertEquals(fExpected, Fibonacci.compute(fInput));
}
}
这将使您的所有测试方法使用相同的参数,因为它们通常会分配给 JUnit class 中的相应字段。关键是这个静态方法中不同实现的实例化(Dagger、Guice、工厂等等)。然后它们将自动传递给构造函数,您将负责将它们分配给您将在测试方法中使用的字段。
如您所见,不使用示例的整数数组,而是将您的实现实例放入其中。有关更多信息,请查看上面的 link。
第二个选项是使用 Zohhak 和来自 https://github.com/piotrturski/zohhak 的注释 @RunWith(ZohhakRunner.class)
。这将允许您按方法而不是按 class 参数化单元测试。这对于工厂实例化可能会更棘手,但可以通过一些工作非常优雅地完成。来自 Zohhak 站点的示例:
@TestWith({
"clerk, 45'000 USD, GOLD",
"supervisor, 60'000 GBP, PLATINUM"
})
public void canAcceptDebit(Employee employee, Money money, ClientType clientType) {
assertTrue( employee.canAcceptDebit(money, clientType) );
}
我会从第一种方法开始,如果你达到了极限,就转向第二种方法。干杯,祝你好运。
Guava 对用 JUnit3 编写的集合实现进行了广泛的测试,如下所示:
/*
* Copyright (C) 2008 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
public class CollectionRemoveTester<E> extends AbstractTester<E> {
@CollectionFeature.Require(SUPPORTS_REMOVE)
@CollectionSize.Require(absent = ZERO)
public void testRemove_present() {
...
}
}
然后使用 TestSuiteBuilder
来测试不同的集合,这些集合传入一组特征和集合类型的生成器,并且一个高度反射的框架将测试方法集识别为 运行 .
我想在 JUnit4 中构建类似的东西,但我不清楚如何去做:构建我自己的 Runner
?理论?到目前为止我最好的猜测是写类似
abstract class AbstractCollectionTest<E> {
abstract Collection<E> create(E... elements);
abstract Set<Feature> features();
@Test
public void removePresentValue() {
Assume.assumeTrue(features().contains(SUPPORTS_REMOVE));
...
}
}
@RunWith(JUnit4.class)
class MyListImplTest<E> extends AbstractCollectionTest<E> {
// fill in abstract methods
}
一般问题类似于:在 JUnit4 中,我如何为接口类型构建一套测试,然后将这些测试应用于各个实现?
您可以使用 JUnit 规则有条件地忽略测试(它们最终会在 maven 报告中被跳过,但可能有一种方法 fiddle 解决这个我不知道的问题)。
这是基于规则 in this article. I changed the rule a bit, see here。
public abstract class AbstractCollectionTest {
@Rule
public ConditionalSupportRule rule = new ConditionalSupportRule();
private Collection<String> collection;
private Set<Class<? extends Feature>> features;
public AbstractCollectionTest(Collection<String> collection,
Class<? extends Feature> ... features) {
this.collection = collection;
this.features = new HashSet<>();
for (Class<? extends Feature> feature : features) {
this.features.add(feature);
}
}
@Test
@ConditionalSupport(condition = SupportsRemove.class)
public void remove() throws Exception {
// ...
System.out.println("test run");
}
private interface Feature {}
public class SupportsRemove implements RunCondition, Feature {
public SupportsRemove() {
}
@Override
public boolean isSatisfied() {
return features.contains(SupportsRemove.class);
}
}
数组列表的示例测试:
public class ArrayListTest extends AbstractCollectionTest {
public ArrayListTest() {
super(new ArrayList<>(), SupportsRemove.class);
}
}
一些不支持删除的列表:
public class UnmodifiableListTest extends AbstractCollectionTest {
public UnmodifiableListTest() {
super(Collections.unmodifiableList(new ArrayList<>()));
}
}
在 Junit 中,您可以使用 categories。例如,该套件将从注释为集成的 AllTestSuite 执行所有测试:
import org.junit.experimental.categories.Categories;
import org.junit.experimental.categories.Categories.IncludeCategory;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Categories.class)
@IncludeCategory(Integration.class)
@Suite.SuiteClasses ({AllTestsSuite.class} )
public class IntegrationTestSuite {}
您也可以使用@ExcludeCategory。这对于删除慢速测试很有用。类别 类 只是普通的旧 Java 类 或接口。例如:
public interface Integration{}
public interface Performance{}
public interface Slow{}
public interface Database{}
您只需相应地注释您的测试:
@Category(Integration.class)
public class MyTest{
@Test
public void myTest__expectedResults(){
[...]
一个测试可能有多个这样的类别:
@Category({Integration.class,Database.class})
public class MyDAOTest{
为简单起见,我通常使用 google 工具箱在测试文件夹中创建一个包含所有 类 的套件:
import org.junit.runner.RunWith;
import com.googlecode.junittoolbox.ParallelSuite;
import com.googlecode.junittoolbox.SuiteClasses;
@RunWith(ParallelSuite.class)
@SuiteClasses({"**/**.class", //All classes
enter code here "!**/**Suite.class" }) //Excepts suites
public class AllTestsSuite {}
这包括在 AllTestSuite 中的所有 类 相同的文件夹和子文件夹中,即使它们没有 _Test 后缀。但是将无法看到不在同一文件夹或子文件夹中的测试。 junit-toolbox 在 Maven 中可用:
<dependency>
<groupId>com.googlecode.junit-toolbox</groupId>
<artifactId>junit-toolbox</artifactId>
<version>2.2</version>
</dependency>
现在您只需执行适合您需要的套件:)
UPDATE:在Spring中有@IfProfileValue注释允许你有条件地执行测试,如:
@IfProfileValue(name="test-groups", values={"unit-tests", "integration-tests"})
@Test
public void testProcessWhichRunsForUnitOrIntegrationTestGroups() {
有关详细信息,请参阅 Spring JUnit Testing Annotations
关于是否构建您自己的 Runner...我认为您不应该立即尝试构建您自己的 Runner,而是参数化您的单元测试。
一个选项是用 @RunWith(Parameterized.class)
注释您的 class 并插入一个用 @Parameters
注释的静态方法,该方法将用于使用 JUnit 测试的构造函数进行实际参数化。下面是我无耻地取自 https://github.com/junit-team/junit/wiki/Parameterized-tests:
@RunWith(Parameterized.class)
public class FibonacciTest {
@Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] {
{ 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 }
});
}
private int fInput;
private int fExpected;
public FibonacciTest(int input, int expected) {
fInput= input;
fExpected= expected;
}
@Test
public void test() {
assertEquals(fExpected, Fibonacci.compute(fInput));
}
}
这将使您的所有测试方法使用相同的参数,因为它们通常会分配给 JUnit class 中的相应字段。关键是这个静态方法中不同实现的实例化(Dagger、Guice、工厂等等)。然后它们将自动传递给构造函数,您将负责将它们分配给您将在测试方法中使用的字段。 如您所见,不使用示例的整数数组,而是将您的实现实例放入其中。有关更多信息,请查看上面的 link。
第二个选项是使用 Zohhak 和来自 https://github.com/piotrturski/zohhak 的注释 @RunWith(ZohhakRunner.class)
。这将允许您按方法而不是按 class 参数化单元测试。这对于工厂实例化可能会更棘手,但可以通过一些工作非常优雅地完成。来自 Zohhak 站点的示例:
@TestWith({
"clerk, 45'000 USD, GOLD",
"supervisor, 60'000 GBP, PLATINUM"
})
public void canAcceptDebit(Employee employee, Money money, ClientType clientType) {
assertTrue( employee.canAcceptDebit(money, clientType) );
}
我会从第一种方法开始,如果你达到了极限,就转向第二种方法。干杯,祝你好运。