6

假设定义了接口

interface Foo {
  int getBaz();
  void doBar();
}

进一步假设合约规定每次调用 doBar 时 baz 都会递增。(好的,这是一段人为的代码,但在这里坚持我)

现在我想提供一个可以提供给 Foo 实现者的单元测试,以便他们可以验证他们是否满足所有合同条件。

class FooTest {
  protected Foo fooImpl;

  @Test
  public void int testBazIncrement()
  {
    int b = fooImpl getBaz();
    fooImpl.doBar();
    Assert.assertEquals( b+1, fooImpl.getBaz();
  }
}

将测试提供给 Foo 的实施者的最佳实践是什么?在我看来,他们需要一种机制来调用 FooTest 并提供 Foo 或 FooFactory 来构造 Foo 实例。此外,如果有很多测试(想想大接口),那么我想将所有这些测试放在一个 FooTest 类中。

是否有关于如何实施此类测试的最佳实践?

4

5 回答 5

2

这是依赖注入的教科书示例。如果你使用 Spring 作为 DI 容器,你可以在fooImpl

@Inject
protected Foo fooImpl;

您的测试需要使用 注释@RunWith(SpringJUnit4ClassRunner.class),并且由接口提供者来配置 Spring 及其实现。

如果没有 DI 容器,您只需创建一个包含所有测试的抽象测试类和一个提供实现对象的抽象方法。

protected abstract Foo createFoo();

由实现提供者对测试进行子类化并实现抽象方法。

class FooImplTest extends FooTestSuper {

@Override
protected Foo createFoo() {
    return new FooImpl();
}

如果您有多个测试,请考虑 JUnit 的@Suite注释。它与 Spring 测试运行器兼容。

于 2013-01-08T10:40:08.413 回答
0

经过深思熟虑和一些死胡同,我开始使用以下模式:

在下面的:

  • [INTERFACE] 指被测试的接口。
  • [CLASS] 是指被测试接口的实现。

构建接口测试,以便开发人员可以测试实现是否符合接口和随附文档中规定的合同。

被测试的主要项目使用 [INTERFACE]ProducerInterface 的实例来创建被测试对象的实例。[INTERFACE]ProducerInterface 的实现必须跟踪测试期间创建的所有实例,并在请求时关闭所有实例。有一个 Abstract[INTERFACE]Producer 可以处理大部分功能,但需要 createNewINTERFACE 实现。

测试

接口测试被标记为 Abstract[INTERFACE]Test。测试通常扩展 Abstract[INTERFACE]ProducerUser 类。此类处理在测试结束时清理所有图表,并为实现者提供一个挂钩以插入他们的 [INTERFACE]ProducerInterface 实现。

一般来说,实现一个测试需要几行代码,如下例所示,新的 Foo 图实现正在测试中。


public class FooGraphTest extends AbstractGraphTest {

        // the graph producer to use while running
        GraphProducerInterface graphProducer = new FooGraphTest.GraphProducer();

        @Override
        protected GraphProducerInterface getGraphProducer() {
                return graphProducer;
        }

        // the implementation of the graph producer.
        public static class GraphProducer extends AbstractGraphProducer {
                @Override
                protected Graph createNewGraph() {
                        return new FooGraph();
                }
        }

}

套房

测试套件被命名为 Abstract[INTERFACE]Suite。套件包含几个测试,用于对被测对象的组件进行所有测试。例如,如果 Foo.getBar() 返回 Bar 接口的实例,则 Foo 套件包括对 Foo 本身的测试以及运行 Bar 测试 Bar。运行套件比运行测试要复杂一些。

套件是使用 JUnit 4 @RunWith(Suite.class) 和 @Suite.SuiteClasses({ }) 注释创建的。这有几个开发人员应该知道的影响:

  1. 套件类在运行期间不会被实例化。
  2. 测试类名称必须在编码时(而不是运行时)已知,因为它们在注释中列出。
  3. 测试的配置必须在类加载的静态初始化阶段进行。

为了满足这些要求,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 泛型可以实现进一步的简化和删除重复代码,但我还没有达到这一点。

于 2013-09-01T09:48:05.003 回答
0

为什么不只拥有一个从单元测试InterfaceImplATestInterfaceImplBTest等中调用的InterfaceTester呢?

例如

@Test
public void testSerialisation()
{
  MyObject a = new MyObject();

  ...

  serialisationTester.testSimpleRoundTrip(a);

  serialisationTester.testEdgeCases(a);

  ... 
}
于 2013-01-08T15:47:10.143 回答
0

你可以实现一个 testDataFactory 来定义你的对象,或者使用 GSon 来创建你的对象(我个人喜欢 GSon,它清晰而快速,你很快就会学会它)。对于测试实现,我建议编写更多测试而不是单个测试。通过这种方式,单元测试可以是独立的,您可以将您的问题隔离在一个封闭的结构中。

声纳

Sonar 是一个对您有很大帮助的工具,可以对您的代码进行分析。您可以从声纳前端看到您的应用程序是如何测试的:

声纳单元测试

如您所见,Sonar 可以向您显示您的代码在哪里进行了测试

于 2013-01-08T08:35:11.047 回答
-1

以下是我对如何进行合格的单元测试的一些想法:

首先,尝试让你的实现类完全测试,这意味着它的所有方法都应该被 UT 覆盖。当您需要重构代码时,这样做会为您节省大量时间。对于您的情况,它可能是:

class FooTest {
    protected Foo fooImpl;

    @Test
    public void testGetBaz() {
    ...
    }

    @Test
    public void testDoBar() {
    ...
    }

}

你会发现你需要检查你的类的内部状态,这并没有错,因为 UT 应该是一种白盒测试。

其次,所有方法都应该单独测试,而不是相互依赖。在我看来,对于您上面发布的代码,它看起来不仅仅是功能测试或集成测试,但它也是必要的。

第三,我认为使用 spring 或其他容器为您组装目标对象不是一个好习惯,这会使您的测试运行相对缓慢,尤其是在需要运行大量测试时。你的类本质上应该是 pojo,如果你的目标对象真的很复杂,你可以在测试类的另一个方法中完成组装。

第四,如果某个类的父接口真的很大,将测试方法分成几组是你应该做的。这里有更多信息。

于 2013-01-08T14:12:28.607 回答