制定一个私人的方法公开testing它…好主意?

版主注意: 这里已经有39个答案了(部分已经被删除) 在发布答案之前,请考虑是否可以在讨论中添加有意义的内容。 你很可能只是重复别人已经说过的话。


我偶尔会发现自己需要在公开课上做一些私人的方法,只是为了写一些unit testing。

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

其他人发现自己这样做,因为我不喜欢这样做? 我个人认为奖金大于公开方法的问题,而这些方法并不真正提供课外的任何服务。

UPDATE

感谢大家的回答,似乎激起了人们的兴趣。 我认为普遍的共识是testing应该通过公共API来进行,因为这是一个课程将被使用的唯一途径,我也同意这一点。 我上面提到的上述情况在哪里我会这样做是罕见的情况下,我认为这样做的好处是值得的。

然而,我可以看到每个人都指出,它永远不会真的发生。 考虑到这一点,我认为改变你的代码来适应testing是一个坏主意 – 毕竟,我认为testing是一种支持工具,如果你愿意,改变一个系统来“支持一个支持工具”是公然的不好的做法。

注意:
这个答案最初是针对这个问题发布的吗?单是unit testing是否有一个很好的理由通过getters公开私有实例variables? 它被合并到这个中,所以对于那里提供的用例可能是特定的。

作为一般性声明,我通常都是为了重构“生产”代码而使其更容易testing。 不过,我认为这不是一个好的方法。 一个好的unit testing(通常)不应该关心类的实现细节,而只关心其可见的行为。 不要将内部堆栈暴露给testing,您可以testing该类在调用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进行unit testing,我绝对不会公开私有方法,以便于testing。

如果你真的想单独testing私有方法,在Java中你可以使用Easymock / Powermock来允许你这样做。

你必须务实,你也应该知道事情难以testing的原因。

“ 听听testing ” – 如果难以testing,是否告诉了你关于你的devise的一些事情? 你可以重构一下这个方法的testing是否微不足道,通过公开的API来testing吗?

迈克尔·菲斯(Michael Feathers)在“ 有效地使用遗产代码 ”中要说的是:

“很多人花了很多时间去研究如何解决这个问题……真正的答案是,如果你有试图testing一个私有方法的冲动,这个方法不应该是私有的;如果公开这个方法困扰你,有可能是因为它是一个单独的责任的一部分,它应该在另一个阶级。 M. Feathers的“ 遗留代码有效地工作” (2005)

正如其他人所说,有人怀疑是单位testing私人方法; unit testing公共接口,而不是私有的实现细节。

也就是说,当我想unit testingC#中私有的东西时,我使用的技术是将可访问性保护从私有降级到内部,然后使用InternalsVisibleTo将unit testing程序集标记为好友程序集。 然后unit testing程序集将被允许将内部结构视为公共对象,但是您不必担心意外添加到公共表面区域。

很多答案build议只testing公共接口,但恕我直言,这是不现实的 – 如果一个方法做了5个步骤,你会想要分别testing这五个步骤,不是所有的一起。 这需要testing所有五种方法, 除了testing以外 ,这些方法可能是private

testing“私有”方法的通常方法是为每个类提供自己的接口,并将“私有”方法public ,但不包含在接口中。 这样,他们仍然可以被testing,但他们不会膨胀的界面。

是的,这将导致文件和类的膨胀。

是的,这确实使publicprivate说明符是多余的。

是的,这是一个痛苦的屁股。

不幸的是,这是我们做代码testing的许多牺牲之一。 也许未来的语言(或甚至是未来版本的C#/ Java)将具有使类和模块可testing性更方便的特征; 但与此同时,我们必须跳过这些环节。


有人会争辩说,每一个步骤应该是自己的阶级 ,但我不同意 – 如果他们都分享国家,没有理由创build五个单独的类,五个方法会做。 更糟的是,这会导致文件和类的膨胀。 此外 ,它会感染模块的公共API – 如果要从另一模块(或者将testing代码包含在同一个模块中进行testing,这意味着将testing代码与产品一起发送),则必须public所有这些类

unit testing应该testing公共契约,这是唯一可以在代码的其他部分使用类的方法。 私有方法是实现的细节,你不应该testing它,只要公共API工作正常,实现没有关系,可以改变而不改变testing用例。

国际海事组织,你应该写你的testing没有深入的假设你的class级如何实施里面。 您可能希望稍后使用另一个内部模型进行重构,但仍然保留以前的实现。

牢记这一点,我build议你专注于testing,无论你的课程目前有什么内部的实施,你的合同仍然存在。 基于性质的testing您的公共API。

如何使它包装私人? 然后你的testing代码可以看到它(和你的包中的其他类),但是它对用户仍然是隐藏的。

但是,真的,你不应该testing私有方法。 这些是实施细节,而不是合同的一部分。 他们所做的一切都应该通过调用公共方法来覆盖(如果他们在那里有没有被公共方法行使的代码,那么应该去)。 如果私有代码太复杂,那么这个类可能会做太多的事情,而不想重构。

公开一个方法是一个很大的承诺。 一旦你这样做,人们将能够使用它,你不能只是改变他们了。

更新 : 我已经在其他许多地方为这个问题添加了一个更加扩展和更完整的答案。 这可以在我的博客上find 。

如果我需要公开testing它,通常会提示被测系统没有遵循单一责任原则 。 因此,应该介绍一个缺less的课程。 在将代码提取到一个新类后,将其公开。 现在您可以轻松testing,并且您正在关注SRP。 你的另一个类只需要通过合成来调用这个新类。

使方法公开/使用语言技巧,如将代码标记为可见,以testingassembly应始终是最后的手段。

例如:

 public class SystemUnderTest { public void DoStuff() { // Blah // Call Validate() } private void Validate() { // Several lines of complex code... } } 

通过引入一个validation器对象来重构这个。

 public class SystemUnderTest { public void DoStuff() { // Blah validator.Invoke(..) } } 

现在我们所要做的就是testingvalidation器是否被正确调用。 validation的实际过程(以前的私有逻辑)可以在纯粹的隔离中进行testing。 将不需要进行复杂的testing来确保validation通过。

一些很好的答案。 有一件事我没有看到提到的是,在testing驱动开发(TDD)中,在重构阶段创build了私有方法(查看Extract Method为重构模式的一个例子),因此应该已经有必要的testing覆盖。 如果做得正确(当然,当涉及到正确性的时候你会得到一大堆意见),你不必担心必须公开私有方法,以便testing它。

为什么不把堆栈pipe理algorithm分成实用程序类? 实用程序类可以pipe理堆栈并提供公共访问器。 它的unit testing可以集中在实现细节上。 对algorithm上棘手的类进行深度testing对于消除边缘情况并确保覆盖率非常有帮助。

然后,您的当前类可以干净地委托给实用程序类而不暴露任何实现细节。 其testing将与其他人build议的分页要求相关。

私有方法通常用作“帮助”方法。 因此,它们只返回基本的值,不会对特定的对象实例进行操作。

如果你想testing它们,你有几个select。

  • 使用reflection
  • 给方法包访问权限

或者你可以创build一个新的类与助手方法作为一个公共的方法,如果它是一个新的类的足够的候选人。

这里有一篇很好的文章 。

在Java中,也可以select将其封装为私有 (即closures可见性修改器)。 如果你的unit testing与被testing的类在同一个包中,那么它应该能够看到这些方法,并且比完全公开的方法更安全一些。

如果你使用C#,你可以使方法内部。 这样你就不会污染公共API。

然后将属性添加到DLL

[汇编:InternalsVisibleTo(“MyTestAssembly”)]

现在所有的方法都可以在MyTestAssembly项目中看到。 也许不完美,但更好的方法是公开私有方法来testing它。

如果需要,可以使用reflection访问私有variables。

但是,真的,你不关心类的内部状态,你只是想testing公共方法返回你期望的情况。

在unit testing方面,绝对不应该增加更多的方法; 我相信你最好在你的first()方法中做一个testing用例,在每个testing之前都会调用它。 那么你可以多次调用 – next()previous()last()来查看结果是否符合你的期望。 我想,如果你没有添加更多的方法到你的class级(只是为了testing的目的),你会坚持“黑匣子”的testing原则;

我想说这是一个坏主意,因为我不确定你是否从中得到任何好处和潜在的问题。 如果你正在改变一个调用的合约,只是为了testing一个私有方法,你不是在testing它将如何使用的类,而是创build一个你从来没有打算发生的假象。

此外,通过宣布这个方法是公开的,在六个月的时间里(忘记了公开方法的唯一原因是为了testing),你(或者如果你把这个项目交给了另一个人)完全不同的人不使用它,导致潜在的意外后果和/或维修噩梦。

首先看看这个方法是否应该被提取到另一个阶级并公开。 如果情况并非如此,那么使其受到封装保护,并在Java中使用@VisibleForTesting进行注释。

在你的更新中,你会说使用公共API进行testing是很好的。 实际上这里有两所学校。

  1. 黑匣子testing

    黑匣子学校说这个class级应该被视为一个黑箱,没有人能看到它里面的实施。 testing这个的唯一方法就是通过公共API – 就像这个类的用户将会使用它一样。

  2. 白盒testing。

    白盒子学校认为自然要用到关于课堂实施的知识,然后testing课堂,知道它应该是应该的。

我真的不能支持讨论。 我只是觉得知道有两种不同的方法来testing一个类(或一个库或其他)是有趣的。

实际上有些情况下,你应该这样做(例如,当你正在实现一些复杂的algorithm)。 只要做到封装私有,这将是足够的。 但是在大多数情况下,你可能有太复杂的类,需要将逻辑分解到其他类中。

你想独立testing的私人方法表明你class上还有另外一个“概念”。 将这个“概念”提取到它自己的类中,并将其作为一个单独的“单元”进行testing。

看看这个video是一个非常有趣的主题。

我倾向于同意,单位testing的奖金超过了提高一些成员的知名度的问题。 一个小小的改进是让它保护和虚拟,然后在testing类中覆盖它来暴露它。

另外,如果它的function是你想单独testing它不build议你的devise缺less一个对象? 也许你可以把它放在一个单独的可testing类中,然后你现有的类只是代表这个新类的一个实例。

我通常将testing类保存在与被testing类相同的项目/程序集中。
这样我只需要internal可见性就可以对函数/类进行testing。

这有点复杂了你的构build过程,它需要过滤掉testing类。 我通过命名所有testing类TestedClassTest并使用正则expression式来过滤这些类来实现此目的。

这当然只适用于你的问题的C#/ .NET部分

我经常会向类中添加一个叫validateverifycheck等的方法,以便可以调用它来testing一个对象的内部状态。

有时候,这个方法被封装在一个ifdef块中(我主要用C ++编写),所以它不会被编译发布。 但是在发行版中通常会提供validation方法来检查程序的对象树。

不,因为有更好的方法去剥皮猫。

某些unit testing工具依赖于类定义中的macros,在testing模式下自动扩展以创build钩子。 非常C风格,但它的作品。

一个更容易的面向对象的方法是做任何你想testing“保护”而不是“私人”。 testing用具从被testing的类inheritance,然后可以访问所有受保护的成员。

或者你去“朋友”选项。 就个人而言,这是C ++的特性,我最不喜欢它,因为它打破了封装规则,但恰巧是C ++如何实现一些function的必要条件,所以嘿嘿。

无论如何,如果你是unit testing,那么你可能需要为这些成员注入值。 白盒发短信是完全有效的。 这真的打破你的封装。

你永远不应该让你的testing决定你的代码。 我不是在谈论TDD或其他DD,我的意思是,正是你问。 您的应用程序是否需要这些方法才能公开。 如果它确实然后testing它们。 如果没有,那就不要让它们公开进行testing。 与variables和其他一样。 让您的应用程序的需求决定代码,并让您的testingtesting满足需求。 (再次,我不是指首先testing或不是我的意思是改变一个class级结构,以达到testing目标)。

相反,你应该“testing更高”。 testing调用私有方法的方法。 但是您的testing应该testing您的应用程序需求,而不是您的“实施决策”。

例如(bod伪代码在这里);

  public int books(int a) { return add(a, 2); } private int add(int a, int b) { return a+b; } 

没有理由testing“添加”,你可以testing“书籍”。

永远不要让你的testing为你做代码devise决定。 testing你得到预期的结果,而不是如何得到这个结果。

正如其他人的评论所广泛指出的那样,unit testing应该关注公共API。 不过,除了利弊之外,你可以通过使用reflection来在unit testing中调用私有方法。 您当然需要确保您的JRE安全性允许。 调用私有方法是Spring Framework与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(); } } } 

Executing B, will print Doing something private. If you really need it, reflection could be used in unit tests to access private instance methods.

Guava has a @VisibleForTesting annotation for marking methods that have enlarged scope (package or public) that they would otherwise. I use a @Private annotation for the same thing.

While the public API must be tested, sometimes it's convenient and sensible to get at stuff that wouldn't normally be public.

什么时候:

  • a class is made significantly less readable, in toto, by breaking it up into multiple classes,
  • just to make it more testable,
  • and providing some test access to the innards would do the trick

it seems like religion is trumping engineering.

I usually leave those methods as protected and place the unit test within the same package (but in another project or source folder), where they can access all the protected methods because the class loader will place them within the same namespace.

In .Net there is a special class called PrivateObject deigned specifically to allow you to access private methods of a class.

See more about it on the MSDN or here on Stack Overflow

(I am wondering that no one has mentioned it so far.)

There are situations though which this is not enough, in which case you will have to use reflection.

Still I would adhere to the general recommendation not to test private methods, however as usual there are always exceptions.

Personally, I have the same problems when testing private methods and this is because the some of the testing tools are limited. It's not good your design to be driven by limited tools if they don't response to your need change the tool not the design. Because your asking for C# i can't propose good testing tools, but for Java there are two powerful tools: TestNG and PowerMock, and you can find corresponding testing tools for .NET platform