Java 有关JUnit测试的字段的访问权限

Java 有关JUnit测试的字段的访问权限,java,unit-testing,junit,Java,Unit Testing,Junit,假设我有一个类,它实现了链表样式的堆栈: 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 TestSt

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

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
的访问权限问题的最佳实践是什么?

我意识到这一点,其中讨论了如何创建访问器,我知道我还可以将我的
堆栈
TestStack
代码放入一个包中,并将
base
字段包设置为私有

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

如果我不想让客户端代码访问字段,那么创建(公共)访问器是一种不好的做法,那么,正确的调用是将字段(或字段本身)包的访问器设置为私有吗?


另一个信息性的讨论了私有方法的选项,但这是关于一个公共方法的,为了测试它,我需要访问一个私有字段,所以“如果你需要访问私有方法/类,那么你做得不对”的许多陈述在这里似乎不适用

看来你已经读了很多问题,但没有触及信息的核心

核心要点是:您不测试生产代码的内部。你感兴趣的是验证公共合同,什么。您希望通过验证方法来避免这样做

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

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

换句话说:您可以模拟一个
StackNode
对象,@InjectMock使用模拟的
StackNode
执行一些反射魔法,为您提供一个
Stack
对象。现在,您可以使用Mockito verify()方法来确保这个
StackNode
能够看到您希望它看到的调用

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

这就是为什么你更喜欢测试什么,而不是如何测试


除此之外:在同一个包(不在同一个项目中)中测试您的类及其测试类生命周期是常见的最佳实践。对一个字段使用一个受包保护的getter,该字段的意思是“仅单元测试”
,这是一个非正式的、但运行良好的解决方案。

我们应该编写使用公共API的测试。但这并不总是可能的。在本例中,我将编写一个测试,同时练习两种方法,
push
pop
,因为这两种方法构成了一个功能堆栈:

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;
    }
}

如果您只是读取那个值,为什么不简单地实现一个getter呢?在任何情况下,为什么您都需要测试它?如果它是一个堆栈,是否有一个public pop()方法可以调用以检查它是否正确保存了值?测试行为,而不是实现。内心的胆量应该无关紧要。您应该能够根据需要更改实现。只要公共方法的签名没有改变,单元测试就不需要改变。不过,这是一个不错的新手问题,因为你实际上坐下来做了一些先前的研究。我投赞成票。。。有乐趣练习投票。。。别忘了在某个时候接受最有用的答案。哈哈,我记下了概念性的想法;你写了一些代码。我想这是一对很好的答案。。。我投了你一票。在你的情况下,你可能想
assertSame
而不是
assertEquals
。“我认为如果它们是不同的例子,那将是令人惊讶的。”迈克尔我不同意。这里面的一切都是关于平衡的。如果加上一个软件包保护的方法,你的生活会比花几个小时从事其他活动轻松10倍。。。那么这就是正确的选择。这取决于上下文;例如,在诸如“这段代码在未来多久会被触动一次”之类的问题上。你只需要有经验来决定具体实现选择的“投资回报”;调用一个方法并不需要。我认为这真的进入了意见空间:我不惜一切代价避免使用反射;你有不同的看法。我只是说我不同意你的陈述中的“绝对性”。@NickZ是的。诀窍在于单元测试——一个非常令人困惑的术语——测试一个功能单元。功能单元是没有确切定义的东西。但是在这种情况下,如果没有
pop
方法,
push
方法就没有意义,因此。。。我相信我在单元测试书/博客文章中读到了这个确切的例子。