类A具有内部类B.类A具有通过getBs,addB和removeB方法提供的B类对象的私有列表。如何对removeB方法进行单元测试?
我希望创建两个相同的B类模拟,添加每个,然后删除其中一个两次(结果是删除它们)。但是,我已经通过失败了解到不会在模拟对象上调用equals方法。
尝试将外部类与其内部类隔离以进行单元测试是不是愚蠢(或不可能)?
示例代码如下
要测试的课程:
import java.util.ArrayList;
import java.util.List;
public class Example {
private List<Inner> inners = new ArrayList<Inner>();
public List<Inner> getInners() {
return inners;
}
public void addInner(Inner i) {
inners.add(i);
}
public void removeInner(Inner i) {
inners.remove(i);
}
/**
* equalityField represents all fields that are used for testing equality
*/
public class Inner {
private int equalityField;
private int otherFields;
public int getEqualityField() {
return equalityField;
}
public Inner(int field) {
this.equalityField = field;
}
@Override
public boolean equals(Object o) {
if (o == null)
return false;
if (o.getClass() != this.getClass())
return false;
Inner other = (Inner) o;
if (equalityField == other.getEqualityField())
return true;
return false;
}
}
}
测试用例没有那么好用:
import static org.junit.Assert.*;
import org.junit.Test;
import org.easymock.classextension.EasyMock;
public class ExampleTest {
@Test
public void testRemoveInner() {
Example.Inner[] mockInner = new Example.Inner[2];
mockInner[0] = EasyMock.createMock(Example.Inner.class);
mockInner[1] = EasyMock.createMock(Example.Inner.class);
Example e = new Example();
e.addInner(mockInner[0]);
e.addInner(mockInner[1]);
e.removeInner(mockInner[0]);
e.removeInner(mockInner[0]);
assertEquals(0, e.getInners().size());
}
}
答案 0 :(得分:5)
你为什么要嘲笑内心阶级?如果我遇到这个问题,我只会按原样使用该类,并测试外部类的行为是否按预期工作。你不需要模拟内部类来做到这一点。
顺便说一句:当重写equals()方法时,建议也重写hashCode()方法。
答案 1 :(得分:5)
首先,你的问题的答案:是的,在单元测试时尝试分离内部和外部类通常是一个坏主意。通常,这是因为两者密切相关,例如Inner只在Outer的上下文中有意义,或者Outer有一个返回接口实现的工厂方法,Inner。如果两者没有真正链接,那么将它们分成两个文件。它使您的测试生活更轻松。
其次,(使用上面的代码作为示例),您实际上不需要模拟上面的代码。只需创建一些实例就可以了。看起来你有足够的合作。您可以随时执行以下操作:
public void testRemoveInner() {
Example.Inner[] inner = new Example.Inner(45);
Example e = new Example();
e.addInner(inner);
e.addInner(inner);
e.removeInner(inner);
assertEquals(0, e.getInners().size());
}
不需要嘲笑。
第三,尝试找出你实际测试的内容。在上面的代码中,您正在测试如果我向列表添加内容,那么我可以将其删除。顺便说一句,你说如果有多个对象是“相等的”;从Collection#remove():
的定义来看,上面的代码没有这样做从中删除指定元素的单个实例 集合,如果它存在(可选操作)。更正式的, 删除元素e,使得(o == null?e == null:o.equals(e)),if 此集合包含一个或多个此类元素。
这真的是你想测试的吗?
第四,如果你实现了equals,也要实现hashCode(参见Overriding equals and hashCode in Java)。
答案 2 :(得分:2)
作为刚刚第一次遇到这种情况的TDD新手,我发现我已经达到了&#34; untestable&#34;作为重构的内部类,如果使用&#34;纯&#34; TDD方法我想知道你是否可以以任何其他方式结束内部类。
问题在于,假设从内部类对外部类对象进行了一次或多次引用,这种特定的重构通常会破坏一个或多个测试。原因很简单:你的模拟对象,如果是spy
,实际上是一个真实对象的包装器
MyClass myClass = spy( new MyClass() );
...但是内部类总是会引用真实对象,因此通常会尝试将模拟应用到myClass
不会起作用。更糟糕的是,即使没有嘲讽,这件事很有可能完全失败并且莫名其妙地只是正常业务。还要注意你的间谍不会为自己运行真正的构造函数方法:很多都会出错。
鉴于我们的测试开发是对质量的投入,在我看来,仅仅说:&#34;好吧,我只是放弃测试&#34,这将是一个可怕的耻辱。 ;
我建议有两种选择:
如果用getter / setter方法替换你的内部类直接对外部类字段的访问(可能是private
,很奇怪)这意味着这是 mock 的方法将被使用......以及模拟的字段。然后,您现有的测试应继续通过。
另一种可能性是重构该内部类以使其成为一个独立的类,其实例替换您的内部类,并将一个或多个测试方法转移到这个新类的新测试类。然后,您将面临绑定事务的(简单)任务,以便对外部类对象的引用进行参数化(即99%的情况,作为构造函数参数传递),然后可以适当地进行模拟。这不应该太困难。虽然您可能需要为外部类中的private
字段添加合适的getter / setter方法和/或创建一个或多个private
方法package-private
。从那时起,内部阶级就变成了一个黑盒子&#34;就测试外部阶级而言。
通过使用任何一种方法,您都没有损失质量。
答案 3 :(得分:0)
您可以使用一个专门的ATest类扩展测试类(A),该类提供一个公共方法,让您可以查看B的私有列表