最佳实践:测试接口合同合规性?

时间:2013-01-08 08:30:50

标签: java testing interface junit

假设定义了接口

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类中。

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

5 个答案:

答案 0 :(得分: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测试运行器兼容。

答案 1 :(得分:0)

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

Sonar

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

sonar unit test

如您所见,Sonar可以向您显示代码的测试位置

答案 2 :(得分:0)

为什么不从一个单元测试中调用 InterfaceTester InterfaceImplATest InterfaceImplBTest 等?

e.g。

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

  ...

  serialisationTester.testSimpleRoundTrip(a);

  serialisationTester.testEdgeCases(a);

  ... 
}

答案 3 :(得分:0)

经过多次思考和一些死胡同后,我开始使用以下模式:

以下内容:

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

构建接口测试,以便开发人员可以测试实现是否符合合同 在界面和随附文档中列出。

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

<强> TESTS

接口测试记为Abstract [INTERFACE]测试。测试通常扩展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();
                }
        }

}

<强> SUITES

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

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

  1. 套件类在运行期间不会被实例化。
  2. 测试类名称必须在编码时(不是运行时)知道,因为它们在注释中列出。
  3. 必须在类加载的静态初始化阶段进行测试配置。
  4. 为了满足这些要求,Abstract [INTERFACE] Suite有一个静态变量,它包含[INTERFACE] ProducerInterface的实例和一些实现“get [INTERFACE] Producer()”的Abstract测试的局部静态实现。方法通过返回静态实例。然后在@ Suite.SuiteClasses批注中使用本地测试的名称。这使得为​​[INTERFACE]实现创建Abstract [INTERFACE]套件的实例非常简单,如下所述。

    
    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使它在类中的任何测试方法之前运行一次。这将设置静态 运行套件之前的图形生成器的实例,以便将其提供给随附的测试。

    <强> FUTURE 我希望通过使用java泛型可以实现进一步的简化和删除重复代码,但我还没有达到这一点。

答案 4 :(得分:-1)

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

首先,尝试对您的实施者类进行全面测试,这意味着UT应该涵盖所有方法。当您需要重构代码时,这样做可以节省大量时间。对于您的情况,可能是:

class FooTest {
    protected Foo fooImpl;

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

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

}

你会发现你需要检查班级的内部状态,并且UT没有任何错误,应该是一种白盒测试。

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

第三,我不认为使用spring或其他容器为你组装目标对象是一个好习惯,这将使你的测试运行相对较慢,特别是当有大量测试运行时。你的类本质上应该是pojo,如果目标对象非常复杂,你可以在测试类中用另一种方法完成汇编。

第四,如果某个类的父接口非常大,那么应该将测试方法分成几个组。 Here是更多信息。