我最近了解了powermock的Whitebox功能。(总之,它允许您直接测试私有方法或直接从单元测试中修改私有成员,同时保持它们的私有性!)
我知道有些想法不赞成对可见方法以外的任何方法进行单元测试,但该死的是,有时你只需要一个简单的测试来确保深层辅助方法正在做它应该做的事情……而不需要花费巨大的开销来准备父方法筛选到你的方法所需的参数和mock。。然后你必须做魔术来提取内部方法的结果。简而言之,有时测试这些内部方法需要最复杂、最难遵循的单元测试。(而且,请注意,我们公司有100%单元测试覆盖率的政策。这些方法必须经过测试!)
我知道一种方法是更改方法访问器以允许单元测试(例如,从private
更改为protected
,但powermock Whitebox允许在不更改源代码的情况下进行直接测试。
尽管如此,妈妈总是说:"仅仅因为你可以,并不意味着你应该。">
这样测试内部方法是否合适?如果是,我应该使用什么经验法则
让我们首先承认这是一场部分宗教的辩论。
我的观点是,测试private
方法通常应该避免,但也有例外。
为什么我不应该测试private
方法
因为你在测试你的应用程序是否工作,而不一定是它是如何工作的。private
方法不是程序公开的API的一部分,它本质上是一个实现细节。
反对绕过封装设置的限制并仍然对其进行测试的一个常见论点是,它可能会破坏测试特定实现的测试。
在"正常"测试中,我们测试public
方法,因为它们构成了我们的程序:如果我们知道所有公共API都能工作,那么我们就知道程序会工作。如果您的应用程序结构良好,那么您将拥有几个API层,这些层会不断地将测试细化为较小的单元,这样您就可以清楚地了解哪些地方可能会出错。
关于private
方法,需要意识到的是,它们总是会在某个时刻被public
方法调用,然后进行测试。private
方法是被测单元的实现细节,因此不应单独测试。
此辅助方法不受API的约束,因此无法保证其效果保持不变;在不调整整体功能的情况下更改实现细节将破坏对该方法的测试。因此,即使您的程序仍然运行良好,但现在您的测试失败了。这些测试是不稳定的:一个不改变你的功能的重构仍然可能导致你不得不删除单元测试,因为它们不再相关(这本身就很危险,因为你必须非常确定测试实际上是冗余的)。
为什么我要测试private
方法
只有少数东西是黑白的,而这不是其中之一。我认为新代码不应该测试private
方法,但这就是我区别的地方:新代码。
如果你继承了一个遗留的代码库,那么体系结构可能不是很漂亮。你可能需要克服重重困难,整体代码流可能会更好。
因此,您要做的第一件事是编写一些单元测试,这些测试保证在您破坏功能时告诉您。到目前为止还不错,我们现在可以进入实际的重构过程了。让我们从这个500行的private
方法开始?
记住前面的备注,您现在必须查看代码流,看看哪些public
方法使用private
方法,并在进行更改时密切关注它们。
为了便于使用,我将在这里编写测试,仅针对应用程序的其他部分测试private
方法的契约:只要该方法的结果没有差异,就知道它不会影响任何其他遗留方法。有了这些知识,您现在可以轻松地重构该方法中的所有内容。当某些东西确实坏了;您可以立即使用您的助手方法,而不必通过公共的API方法。
请记住,我只会在大型重构操作中这样做:这些首先是临时测试,而"实现细节"实际上是500行代码:这是一个有很多细节的细节。
我应该如何测试private
方法
关于如何使这些可测试性,您有几个选项:
反射
我相信这就是WhiteBox所使用的。不过,它引起了一些人的注意:没有静态类型(更改方法名意味着破坏测试),成本高昂,而且使用反射的代码往往更难阅读。
PrivateObject
我还没有研究确切的实现,但我很确定这使用了幕后反射。基本上,它通过一个方便的界面提供了反射方法。我没有看到它被使用太多(部分原因是我会把反思作为绝对的最后手段),但它就在那里。
MSDN
提取
这将是我的第一个方法:这个helper方法是否足够重要,足以使它独立存在?你很可能会对此说"是",因为显然它已经足够重要了,你想要一个明确的测试
公共
最明显的选择是:简单地将其设为public
。这遵循了与提取相同的想法:"它能自立吗?"。这里的区别在于,你仍然认为这是当前类的一部分,你只是提升了它的访问权限,而将它提取到另一个类也表明你谈论的不仅仅是一个helper方法。
内部
此选项采用中间路线:如果我将其设为内部(C#上下文)并使用[InternalsVisibleTo]
属性,则可以将其设成internal
,同时为测试程序集提供对其进行测试的可能性(并且仍然使其远离公共API)。
这带来的风险是,人们只将其视为一个实现细节,并在破坏测试(解释特定实现)的同时更改实现。
此外,这还增加了应用程序和测试之间的耦合。
所有人都认为这取决于你自己的偏好:有几个选项可以测试private
方法,双方都有一些争论。就我个人而言,我会远离测试这些实现细节的麻烦,而是看看我能做些什么来改进我的体系结构。问题很可能会以这种方式自行解决。