如何避免全局状态?

发布于 2024-07-04 22:54:31 字数 404 浏览 9 评论 0原文

所以,我正在阅读谷歌测试博客,它说全局状态很糟糕并且使得编写测试变得困难。 我相信——我的代码现在很难测试。 那么如何避免全局状态呢?

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

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

有没有好的方法来管理状态信息? (或者我错误地理解了全局状态?)

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.

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?)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

逆流 2024-07-11 22:54:37

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 继续:

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

现在您已经看到了,控制器依赖于一个服务层对象,该对象依赖于
一个 dao(数据访问对象)对象,它依赖于数据库对象,并且依赖于
数据库驱动程序、名称等

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

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:

$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

无敌元气妹 2024-07-11 22:54:36

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

您可以研究使用依赖注入框架(例如 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 更好。

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:

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.

扭转时空 2024-07-11 22:54:36

很好的第一个问题。

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

您描述的问题看起来不像全局状态。 至少不是可变状态。 相反,您所描述的似乎通常称为“配置问题”,并且有多种解决方案。 如果您使用 Java,您可能需要研究一下轻量级注入框架,例如 Guice< /a>. 在 Scala 中,这通常可以通过隐式来解决。 在某些语言中,您将能够加载另一个程序来在运行时配置您的程序。 这就是我们过去配置用 Smalltalk 编写的服务器的方式,我使用一个用 Haskell 编写的名为 Xmonad 的窗口管理器,其配置文件只是另一个 Haskell 程序。

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.

瑾夏年华 2024-07-11 22:54:35

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

很难避免某些全局状态,但最好的方法是在应用程序的最高级别使用工厂类,并且该最高级别以下的所有内容都基于依赖项注入。

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文