如何避免全局状态?

时间:2020-03-05 18:47:47  来源:igfitidea点击:

因此,我在阅读Google测试博客时说,全局状态不好,很难编写测试。我相信-我的代码现在很难测试。那么如何避免全局状态?

我使用全局状态(据我了解)最大的用途是管理开发,验收和生产环境之间的关键信息。例如,我有一个名为" Globals"的静态类,其静态成员为" DBConnectionString"。当应用程序加载时,它确定要加载的连接字符串,并填充Globals.DBConnectionString。我在Globals类中加载文件路径,服务器名称和其他信息。

我的某些功能依赖于全局变量。因此,在测试功能时,我必须记住首先设置某些全局变量,否则测试将失败。我想避免这种情况。

是否有管理状态信息的好方法? (还是我对全球状态的理解不正确?)

解决方案

回答

依赖注入是我们要寻找的。而不是让那些函数消失并寻找它们的依赖关系,而是将依赖关系注入到函数中。也就是说,当我们调用函数时,将它们想要的数据传递给它们。这样一来,就可以轻松地围绕类放置测试框架,因为我们可以在适当的地方简单地注入模拟对象。

避免某些全局状态是很难的,但是做到这一点的最佳方法是在应用程序的最高级别使用工厂类,而低于该最高级别的所有内容都基于依赖项注入。

有两个主要好处:其一,测试变得容易得多,其二,应用程序更松散地耦合在一起。我们依赖于能够针对类的接口进行编程,而不是针对其实现。

回答

伟大的第一个问题。

简短的答案:确保应用程序是一个从其所有输入(包括隐式输入)到其输出的函数。

我们正在描述的问题似乎不是全局状态。至少不是可变状态。相反,我们所描述的似乎是通常被称为"配置问题"的东西,并且它具有许多解决方案。如果我们使用的是Java,则可能需要研究诸如Guice之类的轻量级注入框架。在Scala中,这通常可以通过隐式解决。在某些语言中,我们将能够加载其他程序以在运行时配置程序。这就是我们用来配置以Smalltalk编写的服务器的方式,并且我使用了以Haskell编写的名为Xmonad的窗口管理器,其配置文件只是另一个Haskell程序。

回答

请记住,如果测试涉及实际资源(例如数据库或者文件系统),那么我们正在执行的是集成测试而不是单元测试。集成测试需要一些初步的设置,而单元测试应该能够独立运行。

我们可以研究使用依赖注入框架(例如Castle Windsor),但是对于简单的情况,我们可以采用某种中间方法,例如:

public interface ISettingsProvider
{
    string ConnectionString { get; }
}

public class TestSettings : ISettingsProvider
{        
    public string ConnectionString { get { return "testdatabase"; } };
}

public class DataStuff
{
    private ISettingsProvider settings;

    public DataStuff(ISettingsProvider settings)
    {
        this.settings = settings;
    }

    public void DoSomething()
    {
        // use settings.ConnectionString
    }
}

实际上,我们很可能会在实现中从配置文件中读取内容。如果我们愿意,那么可以采用具有可交换配置的功能完备的DI框架是可行的方法,但我认为这至少比使用Globals.ConnectionString更好。