我的问题是关于测试实现许多接口的类。例如,我有这个类:
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.
}
}
我更喜欢第二种解决方案,因为将来我们可以将它实现的接口更改为其他对象,因此使用具体类可能会导致将来测试失败。我不知道这个问题的最佳实践。
谢谢:)
答案 0 :(得分:1)
JayC667已经正确地回答了在这些类型定义的方法测试中,通过其超类型引用类是最好的。但是,为了避免投射,我改变了你的方式:
public class ServiceControllerImplTest {
ServiceController controller;
IDataChanged dataChangeListener;
@Before
public void setUp() {
instance = new ServiceControllerImpl();
controller = instance;
dataChangeListener = instance;
}
}
答案 1 :(得分:1)
我更喜欢第二种解决方案,因为实际上,未来我们可能会将其实现的界面更改为其他对象,因此强制使用混凝土类可能导致将来失败测试。
我想无论如何都会导致测试失败,因为你通常会测试断言是真还是假。问题是:这些测试是否适用于任何IDataChanged
或这些断言是否仅适用于ServiceControllerImpl
?
如果断言仅适用于ServiceControllerImpl
,则使用IDataChanged
而不是ServiceControllerImpl
无关紧要,因为您在使用其他IDataChanged
时必须编辑测试1}} object - 不同的断言。如果您使用其他对象,测试将失败。
您设置单元测试的方式本身为您提供答案。单元测试通常单独测试一个类。这意味着您嘲笑环境。但是模拟环境意味着你知道你测试的类的依赖关系,这是实现细节。因此,您的测试是基于实现而不仅仅是接口编写的。
可以编写仅测试抽象API的测试 - 就像接口一样。但这通常意味着您的测试也是抽象的。 E.g。
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。 E.g。
public class HashSetTest extends SetTest {
@Override
protected Set<String> createSetUnderTest() {
return new HashSet<String>();
}
}
在这种情况下,您可以替换实现,测试必须保持绿色。
但是这里是另一个抽象api的例子,当替换被测对象时并没有多大意义。
如何为所有Runnable
s编写测试?
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定义了具体的状态处理,你可以编写测试它的抽象测试。