为什么Mockito不模拟静态方法?


267

我在这里阅读了一些有关静态方法的主题,并且我认为我理解滥用/过度使用静态方法可能导致的问题。但是我并没有真正理解为什么很难模拟静态方法的原因。

我知道其他模拟框架(例如PowerMock)可以做到这一点,但为什么Mockito不能?

我读了这篇文章,但作者似乎在宗教上反对这个词static,也许这是我的理解不周。

一个简单的解释/链接会很好。


12
只是一个注释:PowerMock本身并不是一个模拟对象库,它只是将这些功能(模拟静态和ctor)添加到其他库的顶部。我们在工作中使用PowerMock + Mockito,它们彼此浮动良好。
Matthias 2010年

Answers:


238

我认为原因可能是模拟对象库通常通过在运行时动态创建类(使用cglib)来创建模拟。这意味着他们要么在运行时实现一个接口(如果我没有记错,那就是EasyMock所做的事情),或者他们从要继承的类继承(如果我没记错的话,那就是Mockito所做的事情)。这两种方法都不适用于静态成员,因为您不能使用继承来覆盖它们。

模拟静态变量的唯一方法是在运行时修改类的字节码,我想这比继承要复杂得多。

这就是我的猜测,这是值得的...


7
顺便说一下,模拟构造函数也是如此。那些也不能通过继承进行更改。
马蒂亚斯(Matthias)2010年

11
这也可能是值得加入一些TDD / TBD支持者认为缺乏静态方法和构造嘲讽为的东西。他们认为,当您发现必须模拟静态方法或构造函数时,这表明类设计不良。例如,当遵循纯粹的IoC方法组装代码模块时,您甚至根本不需要模拟静态变量或ctor(除非它们当然是某些黑盒组件的一部分)。另请参见giorgiosironi.blogspot.com/2009/11/…–
Matthias

200
我确实认为,模拟工具应该可以为您提供所需的东西,而无需假设它们知道哪种方法对您更有利。例如,如果我使用的第三方库利用了我需要模拟的静态方法调用,那么这样做会很好。模拟框架不会为您提供某些功能的想法从根本上是有缺陷的,因为它被视为不良设计。
罗坦(Lo-Tan)

11
@ Lo-Tan-就像在说一门语言应该具备一切能力,而不是假设它比您更懂。那只是您的虚荣心,因为它们像气势汹汹一样脱落。这里的问题是“抗/亲静态”之战还不清楚,框架也不清楚。我同意我们应该两者兼而有之。但是,在事实清楚的,我宁愿一个框架,规定这些事实。那是学习的一种方式-使您步入正轨的工具。所以您自己不必。但是现在每个面条头都可以施加所谓的“良好设计”。“根本有缺陷” ...
nevvermind 2014年

13
@nevvermind嗯?高级语言旨在帮助您并具有必要的抽象,以便您可以专注于重要的部分。测试库是一种工具,我可以使用它来产生更好的质量,并希望设计更好的代码。如果测试/模拟库有局限性,可能意味着当我不得不集成其他人设计不良的代码时无法使用它,那有什么意义呢?似乎没有经过深思熟虑,但是好语言已经存在
Lo-Tan

28

如果您需要模拟静态方法,则可以很好地指示不良设计。通常,您要模拟被测类的依赖关系。如果您的被测类是指静态方法(例如java.util.Math#sin),则意味着被测类正是需要此实现(例如,准确性与速度)。如果您想从一个具体的窦性实施中抽象出来,您可能需要一个接口(您知道它要去哪里)?


3
好吧,我使用静态方法来提供高级抽象,例如“静态持久性外观”。这样的外观使客户端代码远离ORM API的复杂性和底层细节,从而提供了更一致,更易于使用的API,同时还提供了很多灵活性。
罗杰里奥

为什么还要嘲笑它?如果您依赖于静态方法,则“单元”或“模块”不仅是类,还包括“静态持久性外观”。
1

88
是的,但是有时候您可能别无选择,例如,您需要模拟某个第三方类中的静态方法。
Stijn Geukens 2011年

6
是的,但有时我们可能正在处理单例。
Manu Manjunath

唯一无法通过抽象解决的想法是太多的抽象级别...添加抽象层会增加复杂性,并且通常是不必要的。我想到了我看到的一些示例框架,这些示例试图通过将这个简单的调用包装到单个类中来模拟System.currentTimeMillis()。我们最终为每个方法使用单例类,而不是简单地使用方法-只是为了便于测试。然后,当您引入直接调用静态方法而不是通过单例包装器调用静态方法的第三方部门时,测试仍然会失败...
Fr Jeremy Krieg '18

5

如果您也需要模拟静态方法,我确实认为这是代码气味。

  • 静态方法访问常用功能?->使用单例实例并将其注入
  • 第三方代码?->将其包装到您自己的界面/代理中(并在必要时将其设置为单例)

在我看来,唯一一次似乎过大的问题是像Guava这样的库,但是无论如何您都不必模拟这种类型,因为它是逻辑的一部分...(像Iterables.transform(..)之类的东西)
那样,您自己的代码保持整洁,您可以以整洁的方式模拟出所有依赖项,并且拥有一个针对外部依赖项的反腐败层。我已经在实践中看到了PowerMock,而我们所需的所有课程的设计都不尽人意。此外,PowerMock的集成有时会引起严重的问题
(例如https://code.google.com/p/powermock/issues/detail?id=355

PS:私有方法也是如此。我认为测试不应该了解私有方法的细节。如果一个类太复杂以至于它倾向于模拟私有方法,那可能是一个分裂该类的信号。


2
Singleton将使您遇到各种各样的问题,尤其是当您意识到实际上需要多个实例,而现在您需要重构整个系统以实现这种情况时。
里卡多·弗雷塔斯

我没有说过,我建议所有人使用Singleton模式。我的意思是,如果必须在静态实用程序类和提供相同功能的Singleton之间做出选择,我会选择Singleton。并且,如果某个类是否为Singleton,则无论如何都应该由DI框架控制,在我的类I @Inject SomeDependency和配置中,我定义了bind(SomeDependency.class).in(Singleton.class)。因此,如果明天不再是Singleton,我更改一个配置,就是这样。
pete83

@ pete83我听到你兄弟。但是我对测试库或框架有一个问题,要求开发人员更改其设计以满足测试框架的设计/限制。那就是国际海事组织把马车放在马的前面,或者用尾巴摇狗。
马特·坎贝尔

1
这种说法对我来说毫无意义。多年来,单例模式已失宠,原因不胜枚举。什么是“干净”代码?如果我有一个类实例方法,该类实例方法调用返回一些I / O操作的静态帮助器方法,为什么我不希望在测试中对它进行模拟?那糟糕的设计又如何呢?围绕模拟静态方法的所有这些麻烦事都没有加在一起。模拟方法与测试方法相反。如果它难以实现,然后只是说,并用它做
eggmatters

哦,老兄,我从来没有在谈论那种古老的Singleton模式,每个人都Foo.getInstance()到处打电话。我只是在答案中写了singleton来对抗参数“但静态方法不需要创建许多包装对象”。从概念上讲,对我来说,单例上的静态方法和实例方法之间也没有什么区别,只是您无法模拟该单例协作者。但是,单身与否绝对不是我要讲的重点,重点是注入和模拟协作者,如果难以进行测试,则不要调用静态方法。
pete83

4

Mockito返回对象,但static表示“类级别,而不是对象级别”,因此Mockito将为static提供null指针异常。


0

在某些情况下,静态方法可能很难测试,尤其是在需要模拟它们的情况下,这就是为什么大多数模拟框架都不支持它们的原因。我发现这篇博客文章对于确定如何模拟静态方法和类非常有用。


1
使用合适的模拟API时,静态方法的模拟比实例方法的模拟更容易(因为没有实例)。
罗杰里奥

这就像用问题本身来回答问题,这就是为什么很难做到这一点,但这并不是答案。
马赛厄斯

40
我之所以投票,是因为博客文章建议使用昂贵的解决方法(重构生产代码),而不是实际解决将类与它所使用的静态方法隔离的问题。IMO是真正做到这一点的嘲弄工具,不会歧视任何方法。开发人员应该在给定的情况下自由决定使用静态方法是好是坏,而不是被迫沿着一条道路走。
罗杰里奥
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.