主持人注意:这里已经发布了39个答案(其中一些已被删除) . 在您发布答案之前,请考虑您是否可以在讨论中添加有意义的内容 . 你很可能只是重复别人已经说过的话 .
我偶尔会发现自己需要在公共类中创建一个私有方法,只是为它编写一些单元测试 .
通常这是因为该方法包含在类中的其他方法之间共享的逻辑,并且它自己测试逻辑更加整洁,或者另一个原因可能是我想测试同步线程中使用的逻辑而不必担心线程问题 .
其他人发现自己这样做,因为我真的不喜欢这样做吗?我个人认为奖金超过了公开方法的问题,并没有真正提供课外的任何服务......
UPDATE
感谢大家的回答,似乎激起了人们的兴趣 . 我认为普遍的共识是测试应该通过公共API进行,因为这是一个类将被使用的唯一方式,我同意这一点 . 我上面提到的几个我在上面提到的案例是不常见的案例,我认为这样做的好处是值得的 .
但是,我可以看到每个人都指出它永远不应该发生 . 当我考虑更多时,我认为更改代码以适应测试是一个坏主意 - 毕竟我认为测试是一种支持工具,并且如果你愿意,将系统更改为“支持支持工具”,则是公然的不好的做法 .
30 回答
Update :I have added a more expanded and more complete answer to this question in numerous other places. This is can be found on my blog .
如果我需要公开测试它,通常会暗示被测系统不遵循Single Reponsibility Principle . 因此,应该引入一个缺少的类 . 将代码提取到新类后,将其公开 . 现在您可以轻松测试,并且您正在关注SRP . 你的其他类只需要通过合成调用这个新类 .
公开方法/使用langauge技巧,例如将代码标记为可见以测试组合应始终是最后的手段 .
例如:
通过引入验证器对象来重构它 .
现在我们要做的就是测试验证器是否被正确调用 . 实际的验证过程(以前的私有逻辑)可以在纯粹的隔离中进行测试 . 不需要复杂的测试设置来确保验证通过 .
不,因为有更好的皮肤剥皮方法 .
一些单元测试工具依赖于类定义中的宏,当在测试模式下构建时,宏自动扩展以创建钩子 . 非常C风格,但它的工作原理 .
一个更简单的OO习语是让你想要测试“受保护”而不是“私人” . 测试工具继承自被测试的类,然后可以访问所有受保护的成员 .
或者你选择“朋友”选项 . 就个人而言,这是C I的特性,至少因为它打破了封装规则,但它恰好是C实现某些功能的必要条件,所以嘿嘿 .
无论如何,如果你真的需要将值注入这些成员中 . 白盒发短信完全有效 . 这真的会破坏你的封装 .
你永远不应该让你的测试决定你的代码 . 我不是说TDD或其他DD我的意思,正是你的要求 . 您的应用是否需要公开这些方法 . 如果确实如此,则测试它们 . 如果没有,则不要仅仅为了测试而将它们公开 . 与变量和其他相同 . 让您的应用程序需要决定代码,并让您的测试测试满足需求 . (同样,我并不是指先测试或不是我的意思是更改类结构以满足测试目标) .
相反,你应该“测试更高” . 测试调用私有方法的方法 . 但是您的测试应该是测试您的应用程序需求而不是您的“实现决策” .
例如(这里是伪代码);
没有理由测试“添加”你可以测试“书籍” .
永远不要让您的测试为您做出代码设计决策 . 测试你得到了预期的结果,而不是你得到的结果 .
很多答案建议只测试公共界面,但恕我直言这是不现实的 - 如果一个方法做了5步的事情,你会想要分别测试这五个步骤,而不是一起测试 . 这需要测试所有五种方法,否则(测试除外)可能是
private
.测试"private"方法的常用方法是为每个类提供自己的接口,并使"private"方法
public
,但不在接口中包含它们 . 这样,它们仍然可以进行测试,但它们不会使接口膨胀 .是的,这将导致文件和类膨胀 .
是的,这确实使
public
和private
说明符变得多余 .是的,这是一个痛苦的屁股 .
不幸的是,这是众多 sacrifices we make to make code testable 中的一个 . 也许未来的语言(或者甚至是未来的C#/ Java版本)将具有使类和模块可测试性更方便的功能;但与此同时,我们必须跳过这些箍 .
有些人会争辩说这些步骤都应该是be its own class,但我不同意 - 如果它们都共享状态,就没有理由创建五个单独的类,其中有五种方法可以做 . 更糟糕的是,这导致文件和类膨胀 . 另外,它会感染模块的公共API - 如果你想从另一个模块测试它们,那么所有这些类必须是
public
(或者在同一模块中包含测试代码,这意味着将测试代码与产品一起发送) ) .您想要单独测试的私有方法表明您的 class 中还有另一个“概念” . 将“概念”提取到自己的类并将其作为一个测试单独的“单位” .
看一下this video对这个主题的一个非常有趣的看法 .
我想说这是一个坏主意,因为我不确定你是否会获得任何好处以及潜在的问题 . 如果你正在改变一个调用的 Contract ,只是为了测试一个私有方法,你就不会测试它如何被使用,而是创建一个你从未打算过的人工场景 .
此外,通过将该方法声明为公开,可以说在六个月的时间内(忘记将方法设为公开的唯一原因是用于测试之后),您(或者如果您已经将项目交给了)那么完全不同的人赢了使用它,导致潜在的意外后果和/或维护噩梦 .
Guava有一个@VisibleForTesting注释,用于标记扩大范围(包或公共)的方法 . 我使用@Private注释来做同样的事情 .
虽然必须测试公共API,但有时候获取通常不公开的东西是方便和明智的 .
什么时候:
通过将类分解为多个类,使得类的可读性明显降低,
只是为了让它更可测试,
并提供对内部的一些测试访问将成功
宗教似乎胜过工程学 .
我通常将测试类保存在与测试类相同的项目/程序集中 .
这样我只需要
internal
可见性来使函数/类可测试 .这有点使您的构建过程复杂化,这需要过滤掉测试类 . 我通过命名所有测试类
TestedClassTest
并使用正则表达式来过滤这些类来实现此目的 .这当然只适用于您问题的C#/ .NET部分
我通常将这些方法保留为
protected
并将单元测试放在同一个包中(但在另一个项目或源文件夹中),在那里他们可以访问所有受保护的方法,因为类加载器会将它们放在同一个名称空间中 .如果需要,使用反射来访问私有变量 .
但实际上,您并不关心类的内部状态,您只想测试公共方法在您可以预期的情况下返回您期望的内容 .
在.Net中有一个名为
PrivateObject
的特殊类,专门用于访问类的私有方法 .在the MSDN或Stack Overflow上查看更多相关信息
(我想知道到目前为止还没有人提到它 . )
有些情况虽然这还不够,在这种情况下你将不得不使用反射 .
我仍然坚持不测试私有方法的一般建议,但是像往常一样总是有例外 .
首先看看该方法是否应该被提取到另一个类并公开 . 如果不是这种情况,请使用@VisibleForTesting对其进行封装保护并使用Java注释 .
在java中,还可以选择使其成为 package private (即不使用可见性修饰符) . 如果您的单元测试与被测试的类在同一个包中,那么它应该能够看到这些方法,并且比将该方法完全公开更安全一些 .
私有方法通常用作“帮助”方法 . 因此,它们只返回基本值,而不会对特定的对象实例进行操作 .
如果要测试它们,您有几个选项 .
使用反射
授予方法包访问权限
或者,您可以使用辅助方法创建一个新类作为公共方法,如果它对于新类来说是一个足够好的候选者 .
这有一个非常good article here .
我倾向于同意将单元测试的奖金超过提高某些成员的知名度的问题 . 稍微改进是使其受保护和虚拟,然后在测试类中覆盖它以暴露它 .
或者,如果它想要单独测试它的功能,它不会建议您的设计中缺少对象吗?也许你可以把它在一个单独的可测试类中...然后您现有的类只委托给这个新类的一个实例 .
把它打包私有怎么样?然后您的测试代码可以看到它(以及您的包中的其他类),但它仍然对您的用户隐藏 .
但实际上,你不应该测试私有方法 . 这些是实施细节,而不是 Contract 的一部分 . 他们所做的一切都应该通过调用公共方法来解决(如果他们的代码没有被公共方法行使,那么应该去) . 如果私有代码太复杂,那么该类可能做了太多事情而且不需要重构 .
将方法公之于众是一项重大承诺 . 一旦你这样做,人们将能够使用它,你不能再改变它们了 .
就个人而言,我肯定永远不会公开私有方法只是为了让它易于测试 .
如果你真的想要单独测试私有方法,那么在Java中你可以使用Easymock / Powermock来执行此操作 .
你必须务实,你也应该知道难以测试的原因 .
'Listen to the tests ' - if it'难以测试,是告诉你有关你设计的事情吗?您是否可以通过公共API进行重构,以便对这种方法的测试进行重构并轻松覆盖?
这里's what Michael Feathers has to say in ' Working Effectively With Legacy Code“
一些很棒的答案 . 我没有看到提到的一件事是,通过测试驱动开发(TDD),在重构阶段创建私有方法(查看Extract Method以获得重构模式的示例),因此应该已经具有必要的测试覆盖率 . 如果完成正确(当然,你必须担心必须公开私有方法,以便你可以测试它 .
正如其他人所广泛注意到的那样' comments, unit tests should focus on the public API. However, pros/cons and justification aside, you can call private methods in a unit test by using reflection. You would of course need to make sure your JRE security allows it. Calling private methods is something that the Spring Framework employs with it' s ReflectionUtils(参见
makeAccessible(Method)
方法) .这是一个带有私有实例方法的小示例类 .
以及执行私有实例方法的示例类 .
执行B,将打印
Doing something private.
如果您真的需要它,可以在单元测试中使用反射来访问私有实例方法 .我经常会在类中添加一个名为
validate
,verify
,check
等的方法,以便可以调用它来测试对象的内部状态 .有时这个方法被包装在一个ifdef块中(我主要用C编写),所以它不会被编译用于发布 . 但它在发布时通常很有用,可以提供验证方法,让程序的对象树检查事物 .
在单元测试方面,你绝对不应该添加更多的方法;我相信你最好只为你的
first()
方法做一个测试用例,在每次测试之前调用它 . 然后你可以多次调用 -next()
,previous()
和last()
来查看结果是否符合你的预期 . 我想如果你不为你的类添加更多方法(仅用于测试目的),你会坚持"black box"测试原则;如果您使用的是C#,则可以将方法设为内部 . 这样你就不会污染公共API .
然后将属性添加到dll
[assembly:InternalsVisibleTo(“MyTestAssembly”)]
现在,所有方法都可以在MyTestAssembly项目中看到 . 也许并不完美,但更好的方法是将私人方法公之于众,以便进行测试 .
就个人而言,我在测试私有方法时遇到了同样的问题,这是因为一些测试工具是有限的 . 如果你的设计没有响应你的需要而改变工具而不是设计,那么用有限的工具驱动你的设计并不好 . 因为你要求C#i不能提出好的测试工具,但对于Java有两个强大的工具:TestNG和PowerMock,你可以找到相应的.NET平台测试工具
在您的更新中,您说使用公共API进行测试是件好事 . 这里实际上有两所学校 .
黑匣子学校说这个课程应该被认为是一个黑盒子,没有人能看到它内部的实现 . 测试这个的唯一方法是通过公共API - 就像类的用户将使用它一样 .
白盒学校认为自然地使用关于类的实现的知识,然后测试类以知道它的工作原理应该如此 .
我真的不能参与讨论 . 我只是觉得知道测试类(或库或其他)有两种不同的方法会很有趣 .
单元测试应测试公共 Contract ,这是在代码的其他部分中如何使用类的唯一方法 . 私有方法是实现细节,您不应该测试它,只要公共API正常工作,实现无关紧要,并且可以在不更改测试用例的情况下进行更改 .
IMO,您应该编写测试,而不是对您的类在内部实现的方式做出深刻的假设 . 您可能希望稍后使用另一个内部模型重构它,但仍然提供先前实现所提供的相同保证 .
记住这一点,我建议你专注于测试你的 Contract 是否仍然存在,无论你的 class 目前有什么内部实施 . 基于属性的公共API测试 .
作为一般性声明,我认为这将是一个很好的电话 . 一个好的单元测试(通常)不应该't care about the class'实现细节,只关于它的可见行为 . 您可以测试该类在调用
first()
或last()
之后按照您期望的顺序返回页面,而不是将内部堆栈暴露给测试 .例如,考虑这个伪代码:
正如其他人所说,有点怀疑是对私人方法进行单元测试;单元测试公共接口,而不是私有实现细节 .
也就是说,当我想对C#中的私有内容进行单元测试时,我使用的技术是将可访问性保护从私有降级到内部,然后使用InternalsVisibleTo将单元测试程序集标记为友元程序集 . 然后,单元测试组件将被允许将内部处理为公开,但您不必担心意外添加到公共表面区域 .
为什么不将堆栈管理算法拆分为实用程序类?实用程序类可以管理堆栈并提供公共访问器 . 其单元测试可以专注于实现细节 . 对算法上棘手的类进行深入测试非常有助于解决边缘情况并确保覆盖范围 .
然后,您当前的类可以干净地委派给实用程序类,而不会暴露任何实现细节 . 其测试将与其他人推荐的分页要求相关 .
实际上你应该这样做(例如,当你实现一些复杂的算法时) . 只需将它打包为私有,这就足够了 . 但在大多数情况下,你可能有太复杂的类,需要将逻辑分解为其他类 .