我将使用此示例显示我的问题:
我有一个方法foo
。该类有一个子类,它覆盖了这个方法
子类'方法调用超类'方法。我可以验证吗?
我不想测试超类中foo
的作用。我只需要验证它是否被调用。
我知道重构可能会有所帮助(赞成组合而非继承等)但我无法做到这一点。
有没有办法达到我的需要?
以下是我尝试过的简单示例
package tmp;
import org.junit.Test;
import org.mockito.Mockito;
import static org.mockito.Mockito.times;
public class Example {
@Test
public void test() {
// given
ChildClass childClass = new ChildClass();
ChildClass spyChildClass = Mockito.spy(childClass);
// when
spyChildClass.foo(100);
// then
Mockito.verify((BaseClass) spyChildClass, times(1)).foo(101);
}
}
abstract class BaseClass {
public void foo(int n) {
System.out.printf("BaseClass.foo(%d)%n", n);
}
}
class ChildClass extends BaseClass {
@Override
public void foo(int n) {
System.out.printf("ChildClass.foo(%d)%n", n);
super.foo(n + 1);
}
}
这就是结果:
ChildClass.foo(100)
BaseClass.foo(101)
Argument(s) are different! Wanted:
childClass.foo(101);
-> at tmp.Example.test(Example.java:19)
Actual invocation has different arguments:
childClass.foo(100);
-> at tmp.Example.test(Example.java:16)
Expected :childClass.foo(101);
Actual :childClass.foo(100);
<Click to see difference>
显然这不是我想看到的。
我无法修改BaseClass
。我不想测试BaseClass
(我不负责)。我甚至不知道它到底是做什么的。我只需要验证它的方法是否被调用。其他不是我的问题。这是维持BaseClass
的人的问题。
答案 0 :(得分:7)
测试班级的行为,而不是实施。
编写测试,以便调用该方法并期望执行某些操作。接下来检查对象现在代表您现在期望它代表的内容。
如果BaseClass.foo
预计会将某个计数器增加100,但Subclass.foo
会将某个计数器增加50,然后调用超类,验证该计数器现在是150而不是50。
foo
可以做其他事情 - 检查对象的状态而不是它的作用。
答案 1 :(得分:2)
可能的解决方案:使foo
成为最终,并判断子类需要覆盖其他一些方法,即foo
的实现,而不是实际的foo
。
abstract class BaseClass {
private boolean myFooCalled;
public final void foo(int n) {
myFooCalled = false;
fooImpl(int n);
if (!myFooCalled) { ... }
}
public void fooImpl(int n) {
myFooCalled = true;
System.out.printf("BaseClass.foo(%d)%n", n);
}
}
注意:这不是我的头脑,所以(1)我还没有测试过,(2)我不确定这是不是你真正想要的,(3)我&# 39;我不确定你的设计是否应该改进。这是关于&#34;如何确保覆盖方法调用超类方法&#34;的一般答案,而不是根据您的目的定制的答案。
答案 2 :(得分:1)
我毫不犹豫地给出了这个答案,因为这里的每个人(包括OP)都知道你可以这样做......但是为了回答OP的问题,你可以这样做:
而不是
@Override
public void reset() throws IOException{
// ...
super.reset();
}
这样做:
@Override
public void reset() throws IOException{
// ...
callSuperReset();
}
void callSuperReset() throws IOException {
super.reset();
}
... verify
callSuperReset
确实被称为......
我是一个嘲笑的新手(毫无疑问它显示),我想很长一段时间命令只是&#34;你不能创造方法只是为了适合你的测试&#34;。
但在我的previous question中,davidxxx在他的回答中说
事实上,我想说的是:&#34;你不应该创造适合的方法 你的测试和打开应用程序的API是一个不受欢迎的 方式&#34;
鉴于此方法callSuperReset
是包私有的,原则上是否存在任何问题? (除了它的总体不优雅)
答案 3 :(得分:0)
是的,可以编写这样的测试。如果您正在测试的是API的合同,而不是它的实现,那么想要这样做没有任何问题。
这是使用JMockit模拟API对问题中的人为示例进行测试:
@Test
public void subclassShouldObeyContractOfBaseClass(
@Mocked final BaseClass anyBaseInstance)
{
new ChildClass().foo(123);
new Verifications() {{ anyBaseInstance.foo(anyInt); }};
}
如果有必要(由BaseClass#foo(int)
的合同规定),测试还可以验证传递给基本方法的参数值为n + 1
(或其他)。