【问题标题】:Access rights of fields regarding JUnit testsJUnit 测试相关字段的访问权限
【发布时间】:2017-05-17 13:51:43
【问题描述】:

假设我有一个以链表样式实现堆栈的类:

public class Stack {
    private StackNode base;

    public void push(Object item) {
        if (base == null) {base = new StackNode(item);}
        // remaining implementation not shown
    }
}

还有一个 JUnit 测试类:

public class TestStack {
    @Test
    public void testPush() {
        Stack st = new Stack();
        st.push(new Integer(3)); 

        assertEquals((Integer)st.base.getContents(), new Integer(3)); //Error!
        // other test cases not shown
    }
}

我的问题是:解决测试类对base 的访问权限问题的最佳做法是什么?

我知道这个question 讨论了制作访问器,我知道我也可以将我的StackTestStack 代码放入一个包中并将base 字段包私有。

我想知道最好的做法是什么。我知道我不应该在push 的单元测试中使用pop,反之亦然,因为这结合了这些单元测试。

如果我不希望客户端代码访问该字段,我认为创建(公共)访问器是不好的做法,为字段(或字段本身)包创建访问器的正确调用也是如此-私人?

另一个信息丰富的question 讨论了私有方法的选项,但这是关于公共方法的,为了测试它,我需要访问一个私有字段,所以许多语句“如果你需要访问私有方法/类,你做错了”似乎不适用于这里。

【问题讨论】:

  • 如果您只是在读取该值,为什么不简单地为此实现一个 getter?
  • 为什么在任何情况下都需要测试它?如果是堆栈,是否可以调用公共 pop() 方法来检查它是否正确保存了值?
  • 测试行为 - 而不是实现。内部胆量应该无关紧要。您应该能够根据需要更改实现。只要公共方法的签名没有改变,单元测试就不需要改变。
  • 仍然是一个很好的新手问题,因为您实际上坐下来进行了一些先前的研究。我对此的投票......玩得开心练习投票......并且不要忘记在某个时候接受最有帮助的答案。

标签: java unit-testing junit


【解决方案1】:

我们应该编写使用公共 API 的测试。但这并不总是可能的。在这种情况下,我将编写一个测试,同时执行两种方法,pushpop,因为这两个方法构成了一个功能堆栈:

public class Stack {
    private StackNode base;

    public void push(Object item) {
        ...
    }

    // I know this wasn't part of your code
    public Object pop() {
        ...
    }
}

public class TestStack {
    @Test
    public void testOnePush() {
        // GIVEN
        Stack st = new Stack();

        // WHEN
        st.push(new Integer(3)); 
        Object popped = st.pop();

        assertEquals(popped, new Integer(3));
    }
}

如果无法进行这种测试,则可以直接或通过 getter 公开 base。在这种情况下,我更喜欢写一个警告评论,即它仅用于测试:

public class Stack {
    StackNode base; // package-private for testing reasons
}

也可以用方法名称警告其他开发人员:

public class Stack {
    private StackNode base;

    /** For unit tests only!!! */
    public StackNode getBaseForTests() {
         return base;
    }
}

【讨论】:

  • 哈哈,我把概念性的想法放下了;你放下一些代码。我认为这对答案很好......你得到了我的投票。
  • 你可能想要assertSame而不是assertEquals。我认为如果它们是不同的实例,那将是令人惊讶的。
  • @Michael 我不同意。 IT 中的一切都是关于平衡的。如果与花费数小时的其他活动相比,添加一种包保护方法可以让您的生活轻松 10 倍……那么这可能是正确的选择。这取决于上下文;以及诸如“将来多久会触摸此代码”之类的问题。人们只需要经验来确定具体实施选择的“投资回报率”。
  • 重构时反射中断;调用方法不会。我认为这确实进入了意见空间:我不惜一切代价避免使用 reflection;你有不同的意见。我只是说我不同意你陈述中的“绝对性”。
  • @NickZ 是的。诀窍是单元测试 - 一个非常令人困惑的术语 - 测试功能单元。功能单元是没有确切定义的东西。但在这种情况下,如果没有 pop 方法,push 方法将毫无意义,所以......我相信我在单元测试书籍/博客文章中读到了这个确切的例子。
【解决方案2】:

您似乎阅读了很多问题,但没有深入到消息的核心

核心点是:您测试您的生产代码的内部。您有兴趣验证公共合同,what。您希望通过验证如何避免这样做。

换句话说:如果你有一个实现了堆栈的类,那么对这样一个类的理想单元测试......只是检查那个类的对象的行为。含义:您验证推送和弹出值会导致预期结果。弹出一个空堆栈会引发异常,......等等。

如果您真的想检查 如何 部分, 不更改访问修饰符的一种解决方案是使用 Mockito 及其 @InjectMock 注释。

换句话说:您可以模拟一个StackNode 对象,而@InjectMock 使用该模拟的StackNode 为您提供一个Stack 对象。现在您可以使用 Mockito verify() 方法来确保此 StackNode 看到您希望它看到的调用。

当然:这意味着您要投入 大量 时间和精力来编写一个 完全 依赖于 Stack 实现细节的测试。一旦您决定更改它,整个单元测试就会中断;你也必须完全重写它。

这就是为什么你更喜欢测试 what,而不是 how

除此之外:这是常见的最佳实践,您的测试类及其测试类生命在 same 包中(不在同一个 project 中)。并且为一个显示为 // unit test only 的字段提供一个受包保护的 getter 是一种非正式的解决方案,但也非常有效。

【讨论】:

    猜你喜欢
    • 2015-03-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-03
    • 2013-01-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多