我怎样才能避免全球状态?

时间:2021-03-31 06:54:31

So, I was reading the Google testing blog, and it says that global state is bad and makes it hard to write tests. I believe it--my code is difficult to test right now. So how do I avoid global state?

所以,我正在阅读谷歌测试博客,它说全球状态很糟糕,很难编写测试。我相信 - 我的代码现在很难测试。那我该如何避免全球状态呢?

The biggest things I use global state (as I understand it) for is managing key pieces of information between our development, acceptance, and production environments. For example, I have a static class named "Globals" with a static member called "DBConnectionString." When the application loads, it determines which connection string to load, and populates Globals.DBConnectionString. I load file paths, server names, and other information in the Globals class.

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

Some of my functions rely on the global variables. So, when I test my functions, I have to remember to set certain globals first or else the tests will fail. I'd like to avoid this.

我的一些函数依赖于全局变量。因此,当我测试我的函数时,我必须记住首先设置某些全局变量,否则测试将失败。我想避免这个。

Is there a good way to manage state information? (Or am I understanding global state incorrectly?)

有没有一种管理状态信息的好方法? (或者我是否正确地了解全球状态?)

4 个解决方案

#1


11  

Dependency injection is what you're looking for. Rather than have those functions go out and look for their dependencies, inject the dependencies into the functions. That is, when you call the functions pass the data they want to them. That way it's easy to put a testing framework around a class because you can simply inject mock objects where appropriate.

依赖注入是您正在寻找的。不要让这些函数出去寻找它们的依赖关系,而是将依赖项注入函数中。也就是说,当你调用函数时,它们会传递他们想要的数据。这样就很容易在一个类周围放置一个测试框架,因为你可以在适当的地方插入模拟对象。

It's hard to avoid some global state, but the best way to do this is to use factory classes at the highest level of your application, and everything below that very top level is based on dependency injection.

很难避免某些全局状态,但最好的方法是在应用程序的*别使用工厂类,而低于*别的所有内容都基于依赖注入。

Two main benefits: one, testing is a heck of a lot easier, and two, your application is much more loosely coupled. You rely on being able to program against the interface of a class rather than its implementation.

两个主要好处:一,测试更容易,两个,你的应用程序松散耦合。您依赖于能够针对类的接口而不是其实现进行编程。

#2


2  

Keep in mind if your tests involve actual resources such as databases or filesystems then what you are doing are integration tests rather than unit tests. Integration tests require some preliminary setup whereas unit tests should be able to run independently.

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

You could look into the use of a dependency injection framework such as Castle Windsor but for simple cases you may be able to take a middle of the road approach such as:

您可以研究使用依赖注入框架,例如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
    }
}

In reality you would most likely read from config files in your implementation. If you're up for it, a full blown DI framework with swappable configurations is the way to go but I think this is at least better than using Globals.ConnectionString.

实际上,您很可能会从实现中的配置文件中读取。如果您愿意,可以使用可交换配置的完整DI框架,但我认为这至少比使用Globals.ConnectionString更好。

#3


1  

Great first question.

好第一个问题。

The short answer: make sure your application is a function from ALL its inputs (including implicit ones) to its outputs.

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

The problem you're describing doesn't seem like global state. At least not mutable state. Rather, what you're describing seems like what is often referred to as "The Configuration Problem", and it has a number of solutions. If you're using Java, you may want to look into light-weight injection frameworks like Guice. In Scala, this is usually solved with implicits. In some languages, you will be able to load another program to configure your program at runtime. This is how we used to configure servers written in Smalltalk, and I use a window manager written in Haskell called Xmonad whose configuration file is just another Haskell program.

你所描述的问题似乎不像全球状态。至少不是可变状态。相反,您所描述的内容似乎通常被称为“配置问题”,它有许多解决方案。如果您正在使用Java,您可能需要研究像Guice这样的轻量级注入框架。在Scala中,这通常用含义来解决。在某些语言中,您将能够加载另一个程序以在运行时配置程序。这就是我们用来配置用Smalltalk编写的服务器的方法,我使用一个名为Xmonad的Haskell编写的窗口管理器,其配置文件只是另一个Haskell程序。

#4


0  

An example of dependency injection in an MVC setting, here goes:

MVC设置中的依赖注入示例如下:

index.php

$container = new Container();
include_file('container.php');

container.php

container.add("database.driver", "mysql");
container.add("database.name","app");

...

$container.add(new Database($container->get('database.driver', "database.name")), 'database');
$container.add(new Dao($container->get('database')), 'dao');
$container.add(new Service($container->get('dao')));
$container.add(new Controller($container->get('service')), 'controller');

$container.add(new FrontController(),'frontController');

index.php continues here:

index.php继续在这里:

$frontController = $container->get('frontController');
$controllerClass = $frontController->getController($_SERVER['request_uri']);
$controllerAction = $frontController->getAction($_SERVER['request_uri']);
$controller = $container->get('controller');
$controller->$action();

And there you have it, the controller depends on a service layer object which depends on a dao(data access object) object which depends on a database object with depends on the database driver, name etc

你有它,控制器依赖于一个服务层对象,它取决于一个dao(数据访问对象)对象依赖于数据库对象依赖于数据库驱动程序,名称等

#1


11  

Dependency injection is what you're looking for. Rather than have those functions go out and look for their dependencies, inject the dependencies into the functions. That is, when you call the functions pass the data they want to them. That way it's easy to put a testing framework around a class because you can simply inject mock objects where appropriate.

依赖注入是您正在寻找的。不要让这些函数出去寻找它们的依赖关系,而是将依赖项注入函数中。也就是说,当你调用函数时,它们会传递他们想要的数据。这样就很容易在一个类周围放置一个测试框架,因为你可以在适当的地方插入模拟对象。

It's hard to avoid some global state, but the best way to do this is to use factory classes at the highest level of your application, and everything below that very top level is based on dependency injection.

很难避免某些全局状态,但最好的方法是在应用程序的*别使用工厂类,而低于*别的所有内容都基于依赖注入。

Two main benefits: one, testing is a heck of a lot easier, and two, your application is much more loosely coupled. You rely on being able to program against the interface of a class rather than its implementation.

两个主要好处:一,测试更容易,两个,你的应用程序松散耦合。您依赖于能够针对类的接口而不是其实现进行编程。

#2


2  

Keep in mind if your tests involve actual resources such as databases or filesystems then what you are doing are integration tests rather than unit tests. Integration tests require some preliminary setup whereas unit tests should be able to run independently.

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

You could look into the use of a dependency injection framework such as Castle Windsor but for simple cases you may be able to take a middle of the road approach such as:

您可以研究使用依赖注入框架,例如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
    }
}

In reality you would most likely read from config files in your implementation. If you're up for it, a full blown DI framework with swappable configurations is the way to go but I think this is at least better than using Globals.ConnectionString.

实际上,您很可能会从实现中的配置文件中读取。如果您愿意,可以使用可交换配置的完整DI框架,但我认为这至少比使用Globals.ConnectionString更好。

#3


1  

Great first question.

好第一个问题。

The short answer: make sure your application is a function from ALL its inputs (including implicit ones) to its outputs.

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

The problem you're describing doesn't seem like global state. At least not mutable state. Rather, what you're describing seems like what is often referred to as "The Configuration Problem", and it has a number of solutions. If you're using Java, you may want to look into light-weight injection frameworks like Guice. In Scala, this is usually solved with implicits. In some languages, you will be able to load another program to configure your program at runtime. This is how we used to configure servers written in Smalltalk, and I use a window manager written in Haskell called Xmonad whose configuration file is just another Haskell program.

你所描述的问题似乎不像全球状态。至少不是可变状态。相反,您所描述的内容似乎通常被称为“配置问题”,它有许多解决方案。如果您正在使用Java,您可能需要研究像Guice这样的轻量级注入框架。在Scala中,这通常用含义来解决。在某些语言中,您将能够加载另一个程序以在运行时配置程序。这就是我们用来配置用Smalltalk编写的服务器的方法,我使用一个名为Xmonad的Haskell编写的窗口管理器,其配置文件只是另一个Haskell程序。

#4


0  

An example of dependency injection in an MVC setting, here goes:

MVC设置中的依赖注入示例如下:

index.php

$container = new Container();
include_file('container.php');

container.php

container.add("database.driver", "mysql");
container.add("database.name","app");

...

$container.add(new Database($container->get('database.driver', "database.name")), 'database');
$container.add(new Dao($container->get('database')), 'dao');
$container.add(new Service($container->get('dao')));
$container.add(new Controller($container->get('service')), 'controller');

$container.add(new FrontController(),'frontController');

index.php continues here:

index.php继续在这里:

$frontController = $container->get('frontController');
$controllerClass = $frontController->getController($_SERVER['request_uri']);
$controllerAction = $frontController->getAction($_SERVER['request_uri']);
$controller = $container->get('controller');
$controller->$action();

And there you have it, the controller depends on a service layer object which depends on a dao(data access object) object which depends on a database object with depends on the database driver, name etc

你有它,控制器依赖于一个服务层对象,它取决于一个dao(数据访问对象)对象依赖于数据库对象依赖于数据库驱动程序,名称等