何时在单元测试中绕过封装是否合适?

时间:2014-05-16 19:01:05

标签: unit-testing testing junit powermock white-box

我最近了解powermock's Whitebox功能。 (总之,它允许您直接测试私有方法或直接从单元测试修改私有成员 - 同时保持私有!)

我知道除了可见方法之外,还有一些思想框架在单元测试之后不屑一顾,但是有时它只是想要一个简单的测试来确保深层辅助方法正在做它应该做的事情。 ..没有经历可能是巨大的开销来准备父方法过滤到你的方法所需的参数和模拟..然后你只需要做魔术来提取内部方法的结果。简而言之,有时测试这些内部方法需要最复杂且难以遵循的单元测试。 (而且,fyi,我们公司的政策是100%的单位测试覆盖率。这些方法必须经过测试!

我知道one methodology更改方法访问者,以便进行单元测试(例如,从private更改为protected ,但powermock Whitebox允许直接测试而无需更改源代码。

尽管如此,Mamma总是说,"只是因为你可以,并不意味着你应该这样做。"

测试这样的内部方法是否合适?如果是这样,我应该使用的经验法则是什么?

1 个答案:

答案 0 :(得分:4)

让我们开始承认this is a partially religious debate

我的观点是,测试private方法是您应该经常避免的,但也有例外。

为什么我不应该测试private方法?

因为您正在测试您的应用程序是否有效,不一定 它是如何工作的。 private方法不是您的程序公开的API的一部分,它本质上是一个实现细节。

通过封装和仍在测试它来解决限制的一个常见论点是它可能会破坏测试该特定实现的测试。

在"正常"测试时,我们测试public方法,因为它们构成了我们的程序:如果我们知道所有公共API都有效,那么我们就知道程序会起作用。如果您的应用程序结构合理,那么您将拥有多个API层,这些API层不断将测试细化为更小的单元,以便您可以清楚地了解某些地方可能出现的问题。

关于private方法要意识到的事情是,它们总会在某个时刻被public方法调用,这将被测试。 private方法是被测单元的实现细节,因此不应单独测试。

此辅助方法不受API约束,因此无法保证其效果保持不变;在不调整整体功能的情况下更改实现细节现在将破坏您对该方法的测试。因此,即使您的程序仍然完美无缺,但现在您的测试已经破损。这些测试是不稳定的:一个不会改变你的功能的重构仍然会导致你不得不删除单元测试,因为它们不再相关(这本身很危险,因为你必须非常肯定测试实际上多余的。)

为什么要测试private方法?

只有少数东西是黑白的,这不是其中之一。我相信新代码不应该测试private方法,而是我在哪里进行区分:新代码

如果您继承了遗留代码库,那么该架构很可能非常漂亮。您可能需要跳过箍,整个代码流可能会更好。

所以你要做的第一件事是编写一些单元测试,保证在你打破功能时告诉你。到目前为止,我们现在可以进入实际的重构过程。让我们从这个500行private方法开始吧?

记住以前的评论,您现在必须查看代码流并查看public方法使用private方法,并在进行更改时密切关注它们。

为了易于使用,我会在这里编写测试,仅针对您的应用程序的其余部分测试private方法的合同:只要您的方法的结果没有'不同,您知道它不会影响任何其他遗留方法。有了这些知识,您现在可以轻松地重构该方法中的所有内容。什么东西坏了;您可以立即使用辅助方法,而不必使用公共API方法。

请记住,我只会为大型重构操作执行此操作:这些是首先进行的临时测试以及"实现细节"实际上是500行代码:这是一个有很多细节的细节。

我应该如何测试private方法?

这里有一些关于如何使这些可测试的选项:

反射

我相信这就是WhiteBox使用的。但它引起了一些人的注意:没有静态类型(更改方法名称意味着破坏测试),it's expensive和使用反射的代码往往难以阅读。

PrivateObject

我还没有看到确切的实现,但我很确定这会在幕后使用反射。基本上它通过方便的界面提供反射方法。我没有看到它用得太多(部分是因为我会用反射作为绝对的最后手段),但它就在那里。

MSDN

提取

这将是我的第一个方法:这个辅助方法是否足够重要,使其独立?有可能你会说"是"因为显然它已经非常重要,你想要一个明确的测试。

公共

最明显的选择:只需将其设为public即可。这遵循与 Extraction 相同的想法:"它可以独立存在吗?"。这里的不同之处在于你仍然认为这是当前类的一部分,你只是提升它的访问权限,而将它提取到另一个类也表明你不只是谈论辅助方法。

内部

此选项采用中间路径:如果我将其设置为内部(C#上下文)并使用[InternalsVisibleTo]属性,则可以使internal同时为测试程序集提供测试它的可能性(以及仍然远离公共API)。

这带来了人们只将其视为实现细节并在破坏测试时更改实现(代表特定实现)的风险。

此外,这还会增加应用程序与测试之间的耦合。


所有人都认为这取决于您自己的偏好:有几种方法可以测试private方法,双方都有一些争论。就个人而言,我会远离测试此类实现细节的麻烦,而是看看我能做些什么来改进我的架构。机会就是问题会以这种方式解决。