22

我问是因为我正在尝试使用不允许您模拟静态方法的模拟框架(Mockito)。调查它,我发现很多博客文章说你应该尽可能少地使用静态方法,但我很难理解为什么。特别是为什么不修改全局状态并且基本上是辅助方法的方法。例如,我有一个名为的类ApiCaller,它有几个静态方法。静态方法的目的之一是执行 HTTP 调用,处理我们的服务器可能返回的任何自定义问题(例如用户未登录)并返回响应。为了简化,类似:

public class ApiCaller {
...
   public static String makeHttpCall(Url url) {
        // Performs logic to retrieve response and deal with custom server errors
        ...
        return response;
   }
}

要使用它,我所要做的就是调用ApiCaller.makeHttpCall(url) 现在我可以轻松地将其设为非静态方法,例如:

public class ApiCaller {
...
   public String makeHttpCall(Url url) {
        // Performs logic to retrieve response and deal with custom server errors
        ...
        return response;
   }
}

然后使用这个方法调用new ApiCaller().makeHttpCall(),但这似乎是额外的开销。谁能解释为什么这很糟糕,以及是否有更好的解决方案使方法成为非静态的(除了删除关键字之外),以便我可以使用模拟框架来存根这些方法?

谢谢!

4

5 回答 5

9

静态方法的问题在于,当它们与您要测试的系统无关时,它们很难伪造。想象一下这段代码:

public void systemUnderTest() {
    Log.connectToDatabaseForAuditing();
    doLogicYouWantToTest();
}

connectToDatabaseForAuditing()方法是静态的。您不在乎此方法对您要编写的测试有什么作用。但是,现在要测试此代码,您需要一个可用的数据库。

如果它不是静态的,代码将如下所示:

private Logger log; //instantiate in a setter AKA dependency injection/inversion of control

public void systemUnderTest() {
    log.connectToDatabaseForAuditing();
    doLogicYouWantToTest();
}

现在,如果没有数据库,您的测试将很容易编写:

@Before
public void setUp() {
    YourClass yourClass = new YourClass();
    yourClass.setLog(new NoOpLogger());

}

//.. your tests

想象一下当方法是静态的时尝试这样做。inTestMode除了修改记录器以使您在中设置为 true的静态变量setUp()以确保它不连接到数据库之外,我真的想不出办法。

于 2013-01-17T20:19:08.870 回答
2

它的模块化程度较低。ApiCaller相反,您应该使用实例方法定义接口,makeHttpCall()以便将来可以定义单独的实现。

至少,您将始终拥有接口的 2 个实现,即原始版本和模拟版本。

(注意:有一些模拟框架允许你模拟静态方法)

作为附录,虽然在您的特定应用程序中可能并非如此,但通常使用静态方法表示更大的设计疏忽。模块化和可重用性设计应该在整个应用程序中普遍存在,因为即使您现在不需要它,将来也可能需要它,而且事后进行更改会更加困难和耗时。

于 2013-01-17T20:19:01.560 回答
1

PRIVATE 静态辅助方法还不错,事实上,在我工作的大公司中,它们实际上是首选。我一直在使用 Mockito,从调用静态辅助方法的方法访问。

编译器处理静态辅助方法的方式略有不同。创建的字节码将产生一个invokestatic指令,如果您删除静态,结果将是其他指令之一,如invokespecial。不同之处在于,invokestatic 加载类以访问方法,其中 invokespecial 首先将对象从堆栈中弹出。所以可能会有轻微的性能优势(也许没有)。

于 2017-02-18T17:34:49.523 回答
0

当您需要回答自己的问题时,您不能轻易地嘲笑它们。

特别是当它如图所示时:进行 HTTP 调用是昂贵的,并且为单元测试您的代码这样做是没有意义的 - 将其保存用于集成测试。

单元测试需要来自 HTTP 调用的已知响应(和响应代码),如果您使用无法控制的网络调用其他人的服务,则无法做到这一点。

于 2013-01-17T20:26:34.283 回答
0

这个解释在我看来非常直接,并且易于理解。

将实用方法声明为static,通常会使您的代码更易于理解,这是一件好事。

但是,这种方法有一个严重的限制:这样的方法/类不容易被模拟

因此,如果辅助方法有任何外部依赖项(例如数据库),这使得它 - 因此它的调用者 - 难以进行单元测试,最好将其声明为 non-static

这允许依赖注入,从而使方法的调用者更容易进行单元测试。

来源:https ://softwareengineering.stackexchange.com/a/111940/204271

于 2020-11-13T14:49:05.033 回答