我需要在Java中测试一个抽象类,我有一个方法依赖于equals()
方法来提供结果。
public abstract class BaseClass<T extends BaseClass<T>> {
public boolean isCanonical() {
return toCanonical() == this;
}
public T toCanonical() {
T asCanonical = asCanonical();
if (equals(asCanonical)) {
//noinspection unchecked
return (T) this;
}
return asCanonical;
}
protected abstract T asCanonical();
}
正如您所看到的,toCanonical()
方法会将自身与调用抽象方法 asCanonical()
所构建的 asCanonical
进行比较。
为了测试这个方法,我需要创建一个空实现的抽象类,并且使用 mockito 调用两个已实现方法的真实方法,并且为 asCanonical()
返回我的自定义类。
BaseClass aCanonicalClass;
BaseClass aNonCanonicalClass;
@Mock
BaseClass sut;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
// these are the methods under test, use the real ones
Mockito.when(sut.isCanonical())
.thenCallRealMethod();
Mockito.when(sut.toCanonical())
.thenCallRealMethod();
}
通常如果这不是
equals()
方法,我只需要这样做:@Test
public void test_isCanonical_bad() {
// test true
Mockito.when(sut.equals(aCanonicalClass))
.thenReturn(true);
Mockito.when(sut.equals(aNonCanonicalClass))
.thenReturn(false);
Mockito.when(sut.asCanonical())
.thenReturn(aCanonicalClass);
Assert.assertTrue(sut.isCanonical());
// test false
Mockito.when(sut.equals(aNonCanonicalClass))
.thenReturn(true);
Mockito.when(sut.equals(aCanonicalClass))
.thenReturn(false);
Mockito.when(sut.asCanonical())
.thenReturn(aCanonicalClass);
Assert.assertFalse(sut.isCanonical());
}
这会产生以下错误:
org.mockito.exceptions.misusing.MissingMethodInvocationException:
when() requires an argument which has to be 'a method call on a mock'.
For example:
when(mock.getArticles()).thenReturn(articles);
Also, this error might show up because:
1. you stub either of: final/private/equals()/hashCode() methods.
Those methods *cannot* be stubbed/verified.
Mocking methods declared on non-public parent classes is not supported.
2. inside when() you don't call method on mock but on some other object.
Mockito的一个限制是它不允许模拟equals()
和hashcode()
方法。
作为一种解决方法,当我想测试条件equals() = true
时,我只需传递本身,而当我想测试条件equals() = false
时,则传递另一个随机对象即可。
@Test
public void test_isCanonical() {
// test true
Mockito.when(sut.asCanonical())
.thenReturn(sut);
Assert.assertTrue(sut.isCanonical());
// test false
Mockito.when(sut.asCanonical())
.thenReturn(aCanonicalClass);
Assert.assertFalse(sut.isCanonical());
}
但是即使我将实现更改为以下内容,此测试也会通过:
public T toCanonical() {
T asCanonical = asCanonical();
if (this == asCanonical) {
//noinspection unchecked
return (T) this;
}
return asCanonical;
}
甚至可以这样做:
public T toCanonical() {
return asCanonical();
}
这是错误的!应该使用equals进行比较。通过引用进行比较并不是同一件事。
当我到达toCanonical()
方法时,测试变得不可能:
@Test
public void test_toCanonical_bad() {
// test canonical
Mockito.when(sut.equals(aCanonicalClass))
.thenReturn(true);
Mockito.when(sut.equals(aNonCanonicalClass))
.thenReturn(false);
Mockito.when(sut.asCanonical())
.thenReturn(aCanonicalClass);
Assert.assertSame(sut, sut.toCanonical());
// test non-canonical
Mockito.when(sut.equals(aNonCanonicalClass))
.thenReturn(true);
Mockito.when(sut.equals(aCanonicalClass))
.thenReturn(false);
Mockito.when(sut.asCanonical())
.thenReturn(aCanonicalClass);
Assert.assertNotEquals(sut, sut.toCanonical());
Assert.assertSame(aCanonicalClass, sut.toCanonical());
}
当然,这样做是行不通的,而且应用同样的解决方法也没有意义,因为我只需测试函数的返回值是否为
asCanonical()
的返回值即可。@Test
public void test_toCanonical() {
// test canonical
Mockito.when(sut.asCanonical())
.thenReturn(sut);
Assert.assertSame(sut, sut.toCanonical());
// test non-canonical
Mockito.when(sut.asCanonical())
.thenReturn(aCanonicalClass);
Assert.assertNotEquals(sut, sut.toCanonical());
Assert.assertSame(aCanonicalClass, sut.toCanonical());
}
由于我无法模拟
equals()
的结果,因此这两个测试都是无意义的。是否有一种方法可以测试至少使用我提供的对象调用了equals()
?(进行间谍操作)还有其他测试方法吗?
编辑:这只是我正在处理的实际代码的简化版本。
我修改了示例代码,以至少显示
toCanonical()
和asCanonical()
方法应返回相同类的实例(而不仅仅是任何类)。我想在这里测试基类。只有具体实现知道如何构建实际的规范类(
asCanonical()
),或者检查该类是否等于规范类(equals()
)。请注意,如果toCanonical()
等于其规范版本,则返回ITSELF。再次强调equals != same
。实际实现是不可变类。由于它们很多,而且这段代码对每个人都是相同的,所以我将它放在了基类中。
BaseClass
的非空实现并在那里放置所有必要的测试逻辑?目前看起来,你似乎因某种原因禁止自己在没有Mockito的情况下设置测试。 - user3707125BaseClass
的单一实现,为了测试我的抽象类是否按照预期执行,我必须测试一个空实现。如果子类想要覆盖toCanonical()
,那么它可以自由地这样做,并且将有自己的测试。 - Daniele Segato