【问题标题】:How can we test that a class implements many interfaces?我们如何测试一个类实现了许多接口?
【发布时间】:2016-04-03 10:40:12
【问题描述】:

我的问题是关于测试一个实现许多接口的类。比如我有这个类:

public class ServiceControllerImpl extends ServiceController implements IDataChanged, IEventChanged {

}

现在有两种测试方法。第一种是直接在具体类上进行测试。这意味着对象类型是具体的类而不是接口。

public class ServiceControllerImplTest {
    ServiceControllerImpl instance;
    @Before
     public void setUp() {
         instance = new ServiceControllerImpl();
         // you can bring this instance anywhere
     }
}

第二种方法是只在接口上测试。我们必须将此对象类型转换为它实现的所有接口。

public class ServiceControllerImplTest {
    ServiceController instance;       // use interface here 
    IDataChanged dataChangeListener;

    @Before
     public void setUp() {
         instance = new ServiceControllerImpl();
         dataChangeListener = (IDataChanged) instance;
         // instance and dataChangeListener "look like" two different object.
     }
}

我更喜欢第二种解决方案,因为也许将来我们可以将它实现的接口更改为其他对象,因此使用具体类可能会导致将来测试失败。我不知道这个问题的最佳实践。

谢谢:)

【问题讨论】:

  • 是的,用于测试您在接口类型上测试的接口。所以你的第二个解决方案基本上是要走的路。
  • 我相信上面的评论给出了答案,但你的问题需要稍微澄清一下。当您写dataChangeListener = (IDataChanged) dataChangeListener; 时,您的意思是dataChangeListener = (IDataChanged) instance;,对吧?
  • @DaveSchweisguth 是的。我编辑了。非常感谢。
  • 这两种实现之间没有什么可以选择的。注意ServiceController 不是接口。这是一个基类。
  • 该参数适用于任何超类型,无论是接口还是超类。

标签: java unit-testing testing interface


【解决方案1】:

我更喜欢第二种解决方案,因为实际上,也许将来我们可以将它实现的接口更改为其他对象,因此强制使用具体类可能会导致将来测试失败。

我想它无论如何都会导致测试失败,因为您通常会测试断言是真还是假。问题是:这些测试适用于任何IDataChanged 还是这些断言仅适用于ServiceControllerImpl

如果断言仅适用于ServiceControllerImpl,则使用IDataChanged 而不是ServiceControllerImpl 无关紧要,因为在使用另一个IDataChanged 对象时必须编辑测试 - 不同的断言。如果您使用其他对象,测试将失败。

设置单元测试的方式本身会给你答案。单元测试通常单独测试一个类。这意味着您模拟环境。但是模拟环境意味着你知道你测试的类的依赖关系,这是实现细节。因此,您的测试是在实现的基础上编写的,而不仅仅是接口。

可以编写只测试抽象 api 的测试——比如接口。但这通常意味着您的测试也是抽象的。例如

public abstract class SetTest {

    @Test
    public void addAlreadyExistentObject(){
        Set<String> setUnderTest = createSetUnderTest();
        Assert.assertTrue(setUnderTest.isEmpty());

        boolean setChanged = setUnderTest.add("Hello");
        Assert.assertTrue(setChanged);

        setChanged = setUnderTest.add("Hello");
        Assert.assertFalse(setChanged);

        Assert.assertEquals(setUnderTest.size(), 1);

    }

    protected abstract Set<String> createSetUnderTest();

}

然后您可以扩展这些抽象测试来测试具体类的 api。例如

public class HashSetTest extends SetTest {

    @Override
    protected Set<String> createSetUnderTest() {
        return new HashSet<String>();
    }
}

在这种情况下,您可以替换实现并且测试必须保持绿色。

但这里是另一个抽象 api 示例,当替换被测对象并没有真正意义时。 为所有Runnables 编写测试怎么样?

public class RunnableTest {

     @Test
     public void run(){
         Runnable runnable = ...; 

         // What to test here?
         // run is invoked without throwing any runtime exceptions?
         runnable.run();

     }
}

如您所见,在某些情况下,以一种可以轻松替换被测对象的方式编写测试是没有意义的。

如果像 Set api 这样的 api 定义了具体的状态处理,您可以编写抽象测试来测试它。

【讨论】:

    【解决方案2】:

    JayC667 已经正确回答,在由这些类型定义的方法的测试中,最好通过其超类型来引用一个类。但我会稍微改变一下你的做法以避免强制转换:

    public class ServiceControllerImplTest {
        ServiceController controller;
        IDataChanged dataChangeListener;
    
        @Before
         public void setUp() {
             instance = new ServiceControllerImpl();
             controller = instance;
             dataChangeListener = instance;
         }
    }
    

    【讨论】:

      猜你喜欢
      • 2015-08-19
      • 2015-07-28
      • 2010-11-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-23
      相关资源
      最近更新 更多