ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

我读了this article,但作者似乎对static这个词持虔诚的态度,也许是我的理解不佳。

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

附带说明:PowerMock 本身并不是一个模拟对象库,它只是在其他库之上添加了这些功能(模拟静态和 ctor)。我们在工作中使用 PowerMock+Mockito,它们可以很好地相互浮动。

M
Matthias

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

模拟静态的唯一方法是在运行时修改类的字节码,我认为这比继承更复杂一些。

这是我的猜测,因为它的价值......


顺便说一句,模拟构造函数也是如此。这些也不能通过继承来改变。
值得补充的是,一些 TDD/TBD 支持者认为缺少静态方法和构造函数模拟是一件的事情。他们认为,当您发现自己不得不模拟静态方法或构造函数时,这表明类设计不佳。例如,在组装代码模块时遵循纯粹的 IoC 方法时,您甚至不需要首先模拟静态或 ctor(当然,除非它们是某些黑盒组件的一部分)。另请参阅giorgiosironi.blogspot.com/2009/11/…
我确实认为模拟工具应该给你你需要的东西,而不是假设他们知道什么对你更好。例如,如果我正在使用第三方库,该库利用了我需要模拟的静态方法调用,那么能够这样做会很好。认为模拟框架不会为您提供一些功能,因为它被视为糟糕的设计的想法从根本上是有缺陷的。
@Lo-Tan - 这就像说一种语言应该具备一切能力,而不是假设它比你更了解。这只是你的虚荣心,因为它们给人的印象是威严的。这里的问题是“反/亲静态”之战并不明确,框架也是如此。我同意我们应该两者兼得。但是在事实清楚的地方,我更喜欢一个强加这些事实的框架。这是一种学习方式 - 让您保持正轨的工具。所以你自己不必这样做。但现在每一个面条头都可以强加他们所谓的“好设计”。 “根本缺陷”...
@nevvermind 嗯?高级语言旨在帮助您并具有必要的抽象,以便您可以专注于重要部分。测试库是一种工具——我用来生成质量更好、设计更好的代码的工具。测试/模拟库有什么限制,可能意味着当我不得不集成其他人设计不佳的代码时我无法使用它?似乎没有经过深思熟虑,然而,好的语言一直是。
J
Jan

如果你需要模拟一个静态方法,这是一个糟糕设计的有力指标。通常,您模拟被测类的依赖关系。如果您的被测类指的是静态方法 - 例如 java.util.Math#sin - 这意味着被测类完全需要这种实现(例如准确性与速度)。如果你想从一个具体的 sinus 实现中抽象出来,你可能需要一个接口(你知道它会去哪里)?


好吧,我使用静态方法来提供高级抽象,例如“静态持久性外观”。这样的外观使客户端代码远离 ORM API 的复杂性和低级细节,提供了更加一致和易于使用的 API,同时提供了很大的灵活性。
没错,但有时您可能别无选择,例如,如果您需要模拟某个第三方类中的静态方法。
没错,但有时我们可能会处理单例。
唯一认为不能通过抽象解决的就是抽象层级太多……增加抽象层会增加复杂性,而且往往是不必要的。我想到了我所见过的框架示例,这些框架试图通过将这个简单的调用包装到一个类中来模拟 System.currentTimeMillis()。我们最终为每个方法创建一个单例类,而不是简单地拥有方法——只是为了方便测试。然后,当您引入直接调用静态方法而不是通过单例包装器调用静态方法的第 3 方 dep 时,测试无论如何都会失败......
此外,特别是对于 Mockito,模拟也是您断言交互已完成的方式。在我的用例中,我需要为此模拟一个静态方法。我不需要更改它的实现/返回值(事实上,这是我的测试中调用的最后一件事,所以我可以完全忽略它的返回值)。但是因为 Mockito 选择让这些函数重叠,现在我发现自己需要模拟一个静态方法。
G
Gerold Broser

Mockito [3.4.0] can mock static methods!

用 mockito-inline:3.4.0 替换 mockito-core 依赖项。使用静态方法的类:class Buddy { static String name() { return "John"; } } 使用新方法 Mockito.mockStatic(): @Test void lookMomICanMockStaticMethods() { assertThat(Buddy.name()).isEqualTo("John");尝试 (MockedStatic theMock = Mockito.mockStatic(Buddy.class)) { theMock.when(Buddy::name).thenReturn("Rafael"); assertThat(Buddy.name()).isEqualTo("Rafael"); } assertThat(Buddy.name()).isEqualTo("John"); Mockito 仅替换 try 块中的静态方法。


对我来说,一个 Testclass 提供了一些非常好的见解,如何使用新的 statickMock-Feature: StaticMockTest.java(使用 try-Block 非常重要)。另请参阅版本 3.4.2 和 3.4.6 中的错误修正以及原始 issue 1013 的完整性。
@Gerold我试图更多地了解单元测试。如果我们可以直接断言静态方法结果,为什么我们需要模拟?像这样:assertThat(Buddy.name()).isEqualTo("John"); 这个表达式不是已经在测试方法了吗?
@web.learner 1) 这只是一个示例。 2) 你不要模拟你想测试的方法(因为那是没有意义的,因为你只是在测试一个存根)但是你想测试的方法使用的方法. 3)使用 try 块之外的行,您正在调用真正的静态方法。这通常不是人们在测试时想要的(例如,因为在此使用的方法中使用的外部依赖项/资源在测试时甚至都不可用)。请记住,单元测试应该是独立的。 ...续
@web.learner ...继续 - 这就是模拟(即用通用 test double [dummy, fake, stub, mock.] 替换真实方法)发挥作用的地方,您可以定义(然后伪造/模拟/存根)使用的方法应该返回某个特定一旦从您要测试的方法中调用它,就使用/测试用例。
它不仅在尝试中,我正在使用 mockito-inline 4.2.0 并且模拟对象在 try 块之外,手动关闭没有帮助
A
Alex Shesterov

作为 Gerold Broser's answer 的补充,这里有一个使用参数模拟静态方法的示例:

class Buddy {
  static String addHello(String name) {
    return "Hello " + name;
  }
}

...

@Test
void testMockStaticMethods() {
  assertThat(Buddy.addHello("John")).isEqualTo("Hello John");

  try (MockedStatic<Buddy> theMock = Mockito.mockStatic(Buddy.class)) {
    theMock.when(() -> Buddy.addHello("John")).thenReturn("Guten Tag John");
    assertThat(Buddy.addHello("John")).isEqualTo("Guten Tag John");
  }

  assertThat(Buddy.addHello("John")).isEqualTo("Hello John");
}

s
salsinga

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


p
pete83

如果您也需要模拟静态方法,我真的认为这是代码异味。

访问常用功能的静态方法? -> 使用单例实例并注入它

第三方代码? -> 将其包装到您自己的界面/委托中(如果需要,也将其设为单例)

唯一一次对我来说似乎有点矫枉过正,是像 Guava 这样的库,但无论如何你都不应该模拟这种类型,因为它是逻辑的一部分......(像 Iterables.transform(..) 这样的东西)
您自己的代码保持干净,您可以以干净的方式模拟所有依赖项,并且您有一个针对外部依赖项的反腐败层。我在实践中看到过 PowerMock,我们需要它的所有类都设计得很糟糕。此外,PowerMock 的集成有时会导致严重的问题
(例如 https://code.google.com/p/powermock/issues/detail?id=355

PS:私有方法也一样。我认为测试不应该知道私有方法的细节。如果一个类是如此复杂以至于它试图模拟出私有方法,那么它可能是一个拆分该类的标志......


Singleton 会让你遇到各种各样的问题,特别是当你意识到你实际上需要多个实例并且现在你需要重构整个系统来实现这一点时。
我没有说,我向所有人推荐单例模式。我的意思是,如果我必须在提供相同功能的静态实用程序类和 Singleton 之间做出决定,我会选择 Singleton。如果一个类是否是单例类,无论如何都应该由 DI 框架控制,在我的类中我 @Inject SomeDependency 和在我的配置中我定义 bind(SomeDependency.class).in(Singleton.class)。因此,如果明天它不再是单例,我更改一个配置就可以了。
@pete83 我听到你的兄弟了。但是,我有一个测试库或框架的问题,需要开发人员更改他们的设计以满足测试框架的设计/限制。那就是IMO本末倒置,或者尾巴摇着狗。
这种论点对我来说毫无意义。单例模式已经失宠多年了,原因太多了,这里就不一一列举了。什么构成“干净”的代码?如果我有一个类实例方法调用返回一些 I/O 操作的静态辅助方法,为什么我不想在测试中模拟它?那糟糕的设计如何?所有这些围绕模拟静态方法的令人费解的事情并没有加起来。模拟方法与测试方法相反。如果实施起来太难,那么就说出来并完成它
哦,伙计,我从来没有谈论过那种每个人都叫 Foo.getInstance() 的老式单例模式。我只是在答案中写了单例来反驳论点“但是静态方法不需要创建许多包装器对象”。同样在概念上对我来说,单例上的静态方法和实例方法之间几乎没有区别,只是你不能模拟这个单例协作者。但是单例与否绝对不是我想要说明的重点,重点是注入和模拟协作者,如果它使测试变得困难,则不要调用静态方法。
P
PhillC

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


当使用合适的模拟 API 时,模拟静态方法比模拟实例方法更容易(因为没有实例)。
这就像用问题本身来回答问题,这就是为什么很难这样做,这不是答案。
我对它投了反对票,因为博客文章推荐了一种代价高昂的解决方法(重构生产代码),而不是真正解决将类与它碰巧使用的静态方法隔离的问题。 IMO,一个真正完成这项工作的模拟工具,不会歧视任何类型的方法;开发人员应该可以自由地决定在给定情况下使用静态方法是好还是坏,而不是被迫走一条路。