【问题标题】:Correct way to unit test class with inner class使用内部类对类进行单元测试的正确方法
【发布时间】:2012-01-05 20:34:46
【问题描述】:

A 类有内部类 B。A 类有一个 B 类对象的私有列表,它通过 getBs、addB 和 removeB 方法提供。如何对 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());
    }
}

【问题讨论】:

  • 完全不相关,如果不需要外部类的引用,请使用静态内部类

标签: java unit-testing junit easymock


【解决方案1】:

你为什么要嘲笑你的内部阶级?如果我遇到这个问题,我会按原样使用该类并测试外部类的行为是否按预期工作。你不需要模拟内部类来做到这一点。

顺便说一句:当覆盖 equals() 方法时,最好也覆盖 hashCode() 方法。

【讨论】:

  • 关于equals/hashcode:最好也覆盖hashcode,不这样做会破坏Java的一个或最重要的契约,使你的类在hashtables中不可用,并产生一些奇怪的错误运行时。
  • 迟了几年,但我认为我应该提供一些理由来解释为什么有人想要模拟:内部类负责一些不应该在测试环境中发生的网络 IO。跨度>
  • 我认为这个答案没有意义。内部类和外部类之间会有一些交互,很明显这可能需要测试。为什么这是一个公认的答案?
  • 有一百万个理由在 Android 中模拟内部类。
【解决方案2】:

首先,回答您的问题:是的,在单元测试时尝试分离内部类和外部类通常是一个坏主意。通常,这是因为两者密切相关,例如 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)),如果 此集合包含一个或多个此类元素。

这真的是你想要测试的吗?

第四,如果你实现了equals,那么也要实现hashCode(见Overriding equals and hashCode in Java)。

【讨论】:

    【解决方案3】:

    作为第一次遇到这种情况的 TDD 新手,我发现由于重构,我得到了“不可测试”的内部类,如果使用“纯”TDD 方法,我想知道你是否可能以任何其他方式结束内部类。

    问题在于,假设从内部类对外部类对象进行了一个或多个引用,这种特定的重构通常会破坏一个或多个测试。原因很简单:你的模拟对象,如果是spy,实际上是一个真实对象的包装器

    MyClass myClass = spy( new MyClass() );
    

    ...但是内部类将始终引用真实对象,因此尝试将模拟应用于myClass 通常是行不通的。更糟糕的是,即使没有模拟,事情很可能会完全失败,莫名其妙地只是继续其正常业务。另请注意,您的间谍不会为自己运行真正的构造方法:很多错误。

    鉴于我们对测试的开发是对质量的投资,在我看来,如果只是说:“好吧,我将放弃该测试”,那将是一种可怕的耻辱。

    我建议有两种选择:

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

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

    使用任何一种方法都不会降低质量。

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-05-31
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多