为什么将单元测试专用方法视为不良做法?


17

内容:

我目前正在使用Python进行一个小项目。我通常使用一些公开的方法来构造我的类,这些方法已记录在案,但主要处理高级概念(类的用户应了解和使用的内容),以及一堆隐藏的(从下划线开始)负责方法的类。复杂或低级别的处理。

我知道测试对于确保代码的信心并确保以后进行的任何修改都不会破坏以前的行为至关重要。

问题:

为了在受信任的基础上构建更高级别的公共方法,我通常会测试私有方法。我发现查找代码修改是否已引入回归以及在何处更容易。这意味着这些内部测试可能会因次要修订而中断,并且需要修复/替换

但是我也知道,单元测试私有方法至少是一个有争议的概念,或者更经常被认为是不好的做法。原因是:仅应测试公共行为参考

题:

我确实关心以下最佳做法,并希望了解:

  • 为什么在私有/隐藏方法上使用单元测试不好(有什么风险)?
  • 当公共方法可以使用低级和/或复杂处理时,最佳实践是什么?

精度:

  • 这不是一个如何的问题。Python没有真正的隐私概念,隐藏的方法根本没有列出,但是当您知道它们的名称时就可以使用
  • 我从没学过编程规则和模式:我的上一堂课是80年代的...我主要通过尝试和失败以及在Internet上的引用来学习语言(多年来,Stack Exchange是我的最爱)


2
OP,您在哪里听到或读到对私有方法的测试被认为是“不好的”?有多种方法进行单元测试。请参阅单元测试,黑盒测试和白盒测试
John Wu

@JohnWu:我知道白盒测试和黑盒测试之间的区别。但是,即使在白盒测试中,似乎也需要测试私有方法似乎暗示了设计问题。我的问题是试图了解当我跌倒时的最佳路线是什么
Serge Ballesta

2
同样,您在哪里听到或读到,即使在白盒测试中,测试私有方法的需求也暗示了设计问题?在尝试回答之前,我想了解这种信念背后的原因。
John Wu,

换句话说,@ SergeBallesta对那些使您认为测试私有方法是一种不好的做法的文章进行了引用。然后向我们解释为什么您相信他们。
Laiv

Answers:


17

有两个原因:

  1. 通常,当您想测试类的私有方法时,这是一种设计气味(iceberg类,可重复使用的公共组件不足等)。几乎总是存在一些“较大”的问题。

  2. 您可以通过公共接口测试它们(这就是您要测试它们的方式,因为这是客户端调用/使用它们的方式)。通过查看私有方法的所有通过测试的绿灯,您可能会得到错误的安全感。通过公共接口在私有功能上测试边缘情况会更好/更安全。

  3. 通过测试专用方法,您可能会面临严重的测试重复(看起来/感觉非常相似的测试)的风险。当需求改变时,这将产生严重的后果,因为过多的测试将被打破。它还可能使您因测试套件而难以重构……这是最终的讽刺意味,因为测试套件可帮助您安全地进行重新设计和重构!

如果您仍然想测试私有部分(如果它困扰您和YMMV,请不要使用它,但是过去对我有用,请不要使用它):有时为私有函数编写单元测试只是为了确保他们正在按照您认为自己有价值的方式工作(特别是如果您不熟悉某种语言)。但是,在确定它们可以正常工作之后,请删除测试,并始终确保面向公众的测试是可靠的,并且在有人对上述私有功能进行了重大更改的情况下将其捕获。

何时测试私有方法:由于这个答案已经(某种程度上)流行了,我不得不提一句“最佳实践”总是这样:“最佳实践”。这并不意味着您应该教条主义或盲目地这样做。如果您认为应该测试自己的私有方法并且有正当的理由(例如您正在为旧版应用程序编写特性测试),那么请测试您的私有方法。特定情况总是胜过任何一般规则或最佳做法。请注意一些可能出错的问题(请参见上文)。

我有一个关于SO的详细解答,我在这里不再重复:https : //stackoverflow.com/questions/105007/should-i-test-private-methods-or-only-public-ones/ 47401015#47401015


4
原因1:模糊。原因2:如果您的私有帮助器方法不应该成为公共API的一部分,该怎么办?原因3:如果您没有正确设计您的课程,那不是。您的最后一条提示:为什么我要删除一个很好的测试来证明我编写的方法行得通?
罗伯特·哈维

4
@RobertHarvey原因2:可通过公共API间接访问!=属于公共API。如果您的私有功能无法通过公共API进行测试,那么可能是无效代码,应该将其删除吗?或者您的班级确实是冰山一角(原因1),应进行重构。
Frax

5
@RobertHarvey如果您无法通过公共API测试私有函数,请删除它,因为它没有用。
David Arno

1
@RobertHarvey 1:设计气味总是有点主观/模糊,所以可以肯定。但是我列出了一些反模式的具体示例,在我的SO答案中有更多详细信息。2:私有方法不能成为公共API的一部分(根据定义:它们是私有的)...所以我认为您的问题没有多大意义。我想指出的是,如果您有类似bin_search(arr, item)(public)和bin_search(arr, item, low, hi)(private,有很多方法可以进行bin搜索),那么您需要测试的只是面向公众的(bin_search(arr, item)
Matt Messersmith

1
@RobertHarvey 3:首先,我说冒险,而不是保证。其次,声称“如果您正确执行,它就会起作用”是自我实现。例如,“ 如果正确执行,您可以在单个函数中编写操作系统”。这不是错误的:但是它也没有用。关于技巧:之所以删除它,是因为如果您的实现发生了变化(即,您想换出一个私有的impl),那么您的测试套件将成为您的障碍(您将在不应该通过的测试中失败)。
Matt Messersmith

14

鉴于单元测试的主要目的之一是您可以重构程序的内部结构,然后能够验证您是否没有破坏其功能,因此,如果单元测试以如此精细的粒度进行操作会适得其反程序代码的任何更改都要求您重写测试。


不知道为什么您的答案被否决了。简而言之,可以使答案100%正确。
David Arno

3
@DavidArno:也许是因为测试私有方法与测试粒度没有太大关系。它与耦合到实现细节有关。
罗伯特·哈维

11

为私有方法编写单元测试会将单元测试与实现细节联系起来。

单元测试应该在类的外表面(它是公共API)上测试类的行为。单元测试不必了解有关类的内部知识。当需要重构时,针对类的实现细节编写单元测试会很费力。重构几乎肯定会破坏这些测试,因为它们不是稳定API的一部分。

这就是说,为什么可能要编写单元测试你的私有方法?

在单元测试和增量开发之间存在自然的张力。使用REPL(读-评估-打印循环)的软件开发人员可以证明,当您“增长”一个类或功能时,快速编写和测试少量功能是多么有效。在单元测试驱动的环境中执行此操作的唯一好方法是为私有方法编写单元测试,但是这样做会遇到很多麻烦。单元测试需要花费一些时间来编写,您需要一个实际的方法来进行测试,并且您的测试框架需要支持将方法保持私有状态的能力,以免污染您的外部API。

诸如C#和.NET的某些生态系统可以创建类似于REPL的环境(如Linqpad之类的工具可以这样做),但是它们的实用程序受到限制,因为您无权访问您的项目。Visual Studio中的即时窗口不方便;它仍然没有完整的Intellisense,您必须在其中使用完全限定的名称,并且每次使用它都会触发构建。


4
@ user949300在不陷入没有真正的苏格兰人谬论的情况下,对此进行辩论有点困难,但是有很多写得不好的各种测试。从单元测试的角度来看,应该在不知道内部实现细节的情况下测试方法的公共合同。断言某个依赖项被称为X次并非总是错误的:在某些情况下,这是有道理的。您只需要确保这是您实际上想要在被测单元的合同中传达的信息即可。
文森特·萨瓦德

3
@DavidArno:[耸耸肩] 我已经这样做了一段时间。直到Microsoft决定停止在其测试框架中支持代理对象之前,私有方法的单元测试对我来说一直很好。结果没有任何爆炸。我从来没有通过编写私有方法的测试来揭开宇宙的缝隙。
罗伯特·哈维

2
@DavidArno:为什么我会放弃使用一种给我带来好处的完美的技术,只是因为互联网上有人说这是一个不好的主意却没有提供任何理由?
罗伯特·哈维

2
我从单元测试中获得的主要好处是为我提供了一个“安全网”,使我可以修改自己的代码,并有信心知道我的更改没有引入回归。为此,测试私有帮助器方法可以更轻松地找到任何此类回归。当我重构私有助手方法并引入逻辑错误时,我破坏了专用于该私有方法的测试。如果我的单元测试比较笼统,并且只测试了该代码单元的接口,那么发现该问题将更加晦涩难懂。
亚历山大-恢复莫妮卡

2
@Frax当然可以,但是按照这种逻辑,我应该放弃单元测试,转而使用系统范围的集成测试。毕竟,“在大多数情况下,您应该能够修改这些测试以测试相同的行为”
亚历山大–恢复莫妮卡

6

根据我的经验,我发现对内部类,方法进行单元测试通常意味着我必须将经过测试的函数,类删除。创建另一个抽象级别。

这导致更好地遵守单一责任原则。


这应该是公认的答案。
杰克·艾德利

0

我认为这是一个好问题,因为它暴露了测试覆盖率中的常见问题。但是一个好的答案应该告诉您这个问题并不完全正确,因为从理论上讲,您应该对私有方法进行单元测试。这就是为什么它们是私有的

也许更好的问题是“当我想测试私有方法时该怎么办?” ,答案很明显:您应该以一种使测试成为可能的方式来公开它们。现在,这并不一定意味着您应该只公开该方法而已。您很可能希望进行更高的抽象;移至其他库或API,以便您可以在该库上进行测试,而无需在主API中暴露该功能。

请记住,您的方法具有不同的可访问性级别是有原因的,因此您应该始终考虑最终将如何使用您的类。



我试图通过说Python没有真正的隐私概念
Serge Ballesta
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.