主持人注:这里已经有39个答案了(有些已经删除了)。在你发表你的答案之前,考虑一下你是否可以为讨论添加一些有意义的东西。你很可能只是在重复别人已经说过的话。


我偶尔发现自己需要将类中的私有方法设为public,只是为了为它编写一些单元测试。

通常这是因为该方法包含类中其他方法之间共享的逻辑,并且单独测试逻辑更整洁,或者另一个原因可能是我想测试同步线程中使用的逻辑,而不必担心线程问题。

其他人发现他们这样做是因为我不喜欢吗?我个人认为,公开一个方法的好处超过了它在类之外没有提供任何服务的问题……

更新

谢谢大家的回答,似乎引起了大家的兴趣。我认为普遍的共识是测试应该通过公共API进行,因为这是使用类的唯一方式,我非常同意这一点。在我上面提到的几个案例中,我会这样做,这是不常见的情况,我认为这样做的好处是值得的。

然而,我可以看到,每个人都指出它不应该真的发生。再仔细想想,我觉得改变你的代码来适应测试是一个坏主意——毕竟我认为测试在某种程度上是一个支持工具,而改变一个系统来“支持一个支持工具”是明显的坏做法。


当前回答

注意: 这个答案最初是针对以下问题发布的:单独的单元测试是否是通过getter公开私有实例变量的好理由?这是合并到这个,所以它可能是一个特定于这里提出的用例。

一般来说,我通常都支持重构“生产”代码,以使其更容易测试。然而,我不认为这是一个好的决定。一个好的单元测试(通常)不应该关心类的实现细节,只关心它的可见行为。与其将内部堆栈暴露给测试,不如测试类在调用first()或last()后是否按照预期的顺序返回页面。

例如,考虑以下伪代码:

public class NavigationTest {
    private Navigation nav;

    @Before
    public void setUp() {
        // Set up nav so the order is page1->page2->page3 and
        // we've moved back to page2
        nav = ...;
    }

    @Test
    public void testFirst() {
        nav.first();

        assertEquals("page1", nav.getPage());

        nav.next();
        assertEquals("page2", nav.getPage());

        nav.next();
        assertEquals("page3", nav.getPage());
    }

    @Test
    public void testLast() {
        nav.last();

        assertEquals("page3", nav.getPage());

        nav.previous();
        assertEquals("page2", nav.getPage());

        nav.previous();
        assertEquals("page1", nav.getPage());
    }
}

其他回答

单元测试应该测试公共契约,这是在代码的其他部分使用类的唯一方法。私有方法是实现细节,你不应该测试它;只要公共API能够正常工作,实现并不重要,并且可以在不改变测试用例的情况下进行更改。

正如其他人的评论所广泛指出的那样,单元测试应该关注公共API。然而,撇开利弊和论证不考虑,您可以通过使用反射在单元测试中调用私有方法。当然,您需要确保JRE安全性允许这样做。调用私有方法是Spring框架使用它的ReflectionUtils(参见makeAccessible(Method)方法)。

下面是一个带有私有实例方法的小示例类。

public class A {
    private void doSomething() {
        System.out.println("Doing something private.");
    }
}

以及执行私有实例方法的示例类。

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class B {
    public static final void main(final String[] args) {
        try {
            Method doSomething = A.class.getDeclaredMethod("doSomething");
            A o = new A();
            //o.doSomething(); // Compile-time error!
            doSomething.setAccessible(true); // If this is not done, you get an IllegalAccessException!
            doSomething.invoke(o);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }
    }
}

执行B,将打印正在进行私有操作。如果确实需要,可以在单元测试中使用反射来访问私有实例方法。

我想说这是一个坏主意,因为我不确定你是否会得到任何好处,并可能出现问题。如果你改变一个调用的契约,只是为了测试一个私有方法,你并不是在测试这个类将如何使用它,而是在创建一个你从未打算发生的人为场景。

此外,通过将方法声明为公共,在六个月的时间内(在忘记将方法公开的唯一原因是为了测试之后),您(或者如果您已经移交了项目)完全不同的人将不会使用它,从而导致潜在的意想不到的后果和/或维护噩梦。

一些很棒的答案。我没有看到提到的一件事是,在测试驱动开发(TDD)中,私有方法是在重构阶段创建的(关于重构模式的示例,请参阅Extract Method),因此应该已经具有必要的测试覆盖率。如果做得正确(当然,当涉及到正确性时,您将得到各种各样的意见),您应该不必担心必须将私有方法设为公共,以便您可以测试它。

您希望单独测试的私有方法表明在您的类中隐藏了另一个“概念”。将这个“概念”提取到它自己的类中,并将其作为一个单独的“单元”进行测试。

看看这个关于这个话题的有趣视频。