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

Lim*_*awk 7 testing junit unit-testing powermock white-box

我最近开始意识到powermock的Whitebox功能.(总之,它允许您直接测试私有方法或直接从单元测试修改私有成员 - 同时保持私有!)

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

我知道一种方法更改方法访问器以允许单元测试(例如,从更改privateprotected,但powermock Whitebox允许直接测试而不更改源代码.

不过,妈妈总是说,"只因为你可以,并不意味着你应该这样做."

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

Jer*_*vel 4

首先让我们承认这是一场部分宗教辩论

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

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

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

反对解决封装带来的限制并仍然对其进行测试的一个常见论点是,它可能会破坏测试特定实现的测试。

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

关于方法要认识到的一点private是,它们总是会在某个时刻被public方法调用,并进行测试。方法private是被测单元的实现细节,因此不应单独测试。

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

我为什么要测试private方法?

只有少数事情是黑白分明的,这不是其中之一。我相信新代码不应该测试private方法,但这就是我做出区分的地方:新代码

如果您继承了遗留代码库,那么架构很可能不是很漂亮。您可能需要跳过重重困难,整体代码流程可能会更好。

因此,您要做的第一件事就是编写一些单元测试,保证在您破坏功能时告诉您。到目前为止一切顺利,我们现在可以开始实际的重构过程了。让我们从这 500 行开始private方法开始吧?

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

为了易于使用,我会在这里编写测试来测试该合同private方法与应用程序其余部分的契约:只要该方法的结果没有不同,您就知道它不会有差异影响任何其他遗留方法。有了这些知识,您现在可以轻松地重构该方法内的所有内容。当某些东西确实损坏时;您可以立即使用您的辅助方法,而不必通过公共 API 方法。

请记住,我只会对大型重构操作执行此操作:这些首先是临时测试,“实现细节”实际上是 500 行代码:这是一个本身包含大量细节的细节。

我应该如何测试private方法?

关于如何使这些可测试,您有几个选择:

反射

我相信这就是 WhiteBox 使用的方法。但它引起了一些人的注意:没有静态类型(改变方法名意味着破坏测试),它很昂贵,并且使用反射的代码往往更难阅读。

私有对象

我还没有看过确切的实现,但我很确定这在幕后使用了反射。基本上它通过一个方便的接口提供反射方法。我还没有看到它被大量使用(部分原因是我将使用反射作为绝对的最后手段),但它就在那里。

微软软件定义网络

萃取

这将是我的第一个方法:这个辅助方法是否重要到足以使其独立?您很可能对此说“是”,因为显然它已经足够重要,您想要对其进行明确的测试。

民众

最明显的选择:简单地去做public这遵循与提取相同的想法:“它能独立存在吗?”。这里的区别在于,您仍然将其视为当前类的一部分,您只是提升了它的访问权限,而将其提取到不同的类也表明您谈论的不仅仅是一个辅助方法。

内部的

此选项采用中间路线:如果我将其设为内部(C# 上下文)并使用该[InternalsVisibleTo]属性,则可以将其设为internal(并且仍然使其远离公共 API)。

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

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


所有考虑都取决于您自己的喜好:测试private方法有多种选择,双方都有一些论点。就我个人而言,我会避免测试此类实现细节的麻烦,而是看看我可以做些什么来改进我的架构。问题很可能会以这种方式自行解决。