我在这里阅读了一些关于静态方法的主题,我想我理解滥用/过度使用静态方法可能导致的问题。但我并没有真正深入了解为什么很难模拟静态方法。
我知道其他模拟框架,比如 PowerMock,可以做到这一点,但为什么 Mockito 不能呢?
我读了this article,但作者似乎对static
这个词持虔诚的态度,也许是我的理解不佳。
一个简单的解释/链接会很棒。
我认为原因可能是模拟对象库通常通过在运行时动态创建类来创建模拟(使用 cglib)。这意味着他们要么在运行时实现一个接口(如果我没记错的话,这就是 EasyMock 所做的),或者他们从类继承来模拟(如果我没记错的话,这就是 Mockito 所做的)。这两种方法都不适用于静态成员,因为您不能使用继承来覆盖它们。
模拟静态的唯一方法是在运行时修改类的字节码,我认为这比继承更复杂一些。
这是我的猜测,因为它的价值......
如果你需要模拟一个静态方法,这是一个糟糕设计的有力指标。通常,您模拟被测类的依赖关系。如果您的被测类指的是静态方法 - 例如 java.util.Math#sin - 这意味着被测类完全需要这种实现(例如准确性与速度)。如果你想从一个具体的 sinus 实现中抽象出来,你可能需要一个接口(你知道它会去哪里)?
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
assertThat(Buddy.name()).isEqualTo("John");
这个表达式不是已经在测试方法了吗?
try
块之外的行,您正在调用真正的静态方法。这通常不是人们在测试时想要的(例如,因为在此使用的方法中使用的外部依赖项/资源在测试时甚至都不可用)。请记住,单元测试应该是独立的。 ...续
作为 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");
}
Mockito 返回对象,但静态表示“类级别,而不是对象级别”,因此 mockito 将为静态提供空指针异常。
如果您也需要模拟静态方法,我真的认为这是代码异味。
访问常用功能的静态方法? -> 使用单例实例并注入它
第三方代码? -> 将其包装到您自己的界面/委托中(如果需要,也将其设为单例)
唯一一次对我来说似乎有点矫枉过正,是像 Guava 这样的库,但无论如何你都不应该模拟这种类型,因为它是逻辑的一部分......(像 Iterables.transform(..) 这样的东西)
您自己的代码保持干净,您可以以干净的方式模拟所有依赖项,并且您有一个针对外部依赖项的反腐败层。我在实践中看到过 PowerMock,我们需要它的所有类都设计得很糟糕。此外,PowerMock 的集成有时会导致严重的问题
(例如 https://code.google.com/p/powermock/issues/detail?id=355)
PS:私有方法也一样。我认为测试不应该知道私有方法的细节。如果一个类是如此复杂以至于它试图模拟出私有方法,那么它可能是一个拆分该类的标志......
@Inject SomeDependency
和在我的配置中我定义 bind(SomeDependency.class).in(Singleton.class)
。因此,如果明天它不再是单例,我更改一个配置就可以了。
Foo.getInstance()
的老式单例模式。我只是在答案中写了单例来反驳论点“但是静态方法不需要创建许多包装器对象”。同样在概念上对我来说,单例上的静态方法和实例方法之间几乎没有区别,只是你不能模拟这个单例协作者。但是单例与否绝对不是我想要说明的重点,重点是注入和模拟协作者,如果它使测试变得困难,则不要调用静态方法。
在某些情况下,静态方法可能难以测试,尤其是当它们需要被模拟时,这就是大多数模拟框架不支持它们的原因。我发现 this 博客文章对于确定如何模拟静态方法和类非常有用。