用内部类对单元测试类的正确方法

时间:2011-11-24 18:01:32

标签: java unit-testing junit easymock

类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());
    }
}

4 个答案:

答案 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,这将是一个可怕的耻辱。 ;

我建议有两种选择:

  1. 如果用getter / setter方法替换你的内部类直接对外部类字段的访问(可能是private,很奇怪)这意味着这是 mock 的方法将被使用......以及模拟的字段。然后,您现有的测试应继续通过。

  2. 另一种可能性是重构该内部类以使其成为一个独立的类,其实例替换您的内部类,并将一个或多个测试方法转移到这个新类的新测试类。然后,您将面临绑定事务的(简单)任务,以便对外部类对象的引用进行参数化(即99%的情况,作为构造函数参数传递),然后可以适当地进行模拟。这不应该太困难。虽然您可能需要为外部类中的private字段添加合适的getter / setter方法和/或创建一个或多个private方法package-private。从那时起,内部阶级就变成了一个黑盒子&#34;就测试外部阶级而言。

  3. 通过使用任何一种方法,您都没有损失质量。

答案 3 :(得分:0)

您可以使用一个专门的ATest类扩展测试类(A),该类提供一个公共方法,让您可以查看B的私有列表