经过深思熟虑和一些死胡同,我开始使用以下模式:
在下面的内容中:
- [INTERFACE] 指正在测试的接口。
- [CLASS] 指正在测试的接口实现。
接口测试是为了让开发人员测试实现是否符合接口和相关文档中规定的契约。
主要的测试项使用 [INTERFACE]ProducerInterface 的实例来创建正在测试的对象的实例。[INTERFACE]ProducerInterface 的实现必须跟踪测试期间创建的所有实例,并在请求时关闭它们。有一个 Abstract[INTERFACE]Producer 处理大部分功能,但需要一个 createNewINTERFACE 实现。
测试
接口测试被标记为 Abstract[INTERFACE]Test。测试通常扩展 Abstract[INTERFACE]ProducerUser 类。该类在测试结束时处理所有图形的清理,并提供了一个钩子,供实现者插入他们的 [INTERFACE]ProducerInterface 实现。
通常,要实现一个测试需要几行代码,如下面的示例所示,其中正在测试新的 Foo 图实现。
public class FooGraphTest extends AbstractGraphTest {
GraphProducerInterface graphProducer = new FooGraphTest.GraphProducer();
@Override
protected GraphProducerInterface getGraphProducer() {
return graphProducer;
}
public static class GraphProducer extends AbstractGraphProducer {
@Override
protected Graph createNewGraph() {
return new FooGraph();
}
}
}
测试套件
测试套件的命名方式为Abstract[INTERFACE]Suite。套件包含多个测试,用于测试对象下各组件的所有测试。例如,如果Foo.getBar()返回Bar接口的实例,则Foo套件包括对Foo本身的测试以及运行Bar测试的测试。运行测试套件比运行单个测试要复杂一些。
使用JUnit 4的@RunWith(Suite.class)和@Suite.SuiteClasses({ })注释创建测试套件。这会产生几个开发人员应该知道的影响:
- 在运行期间,套件类不会被实例化。
- 测试类名称必须在编码时(而不是运行时)已知,因为它们在注释中列出。
- 测试的配置必须在类加载的静态初始化阶段进行。
为了满足这些要求,Abstract[INTERFACE]Suite具有一个静态变量,它保存了[INTERFACE]ProducerInterface的实例以及一些本地静态实现的抽象测试,这些测试通过返回静态实例来实现“get[INTERFACE]Producer()”方法。然后在@Suite.SuiteClasses注释中使用本地测试的名称。如下所述,这使得为[INTERFACE]实现创建Abstract[INTERFACE]Suite的实例相当简单。
public class FooGraphSuite extends AbstractGraphSuite {
@BeforeClass
public static void beforeClass() {
setGraphProducer(new GraphProducer());
}
public static class GraphProducer extends AbstractGraphProducer {
@Override
protected Graph createNewGraph() {
return new FooGraph();
}
}
}
请注意,beforeClass() 方法被注释为 @BeforeClass。@BeforeClass 会导致它在类中的任何测试方法之前运行一次。这将在套件运行之前设置图形生成器的静态实例,以便提供给封闭测试。
未来展望
我预计通过使用 Java 泛型可以进一步简化和删除重复代码,但我还没有达到那个阶段。