【问题标题】:Refactoring out Singletons/Globals to use Dependency Injection for Unit Testing重构单例/全局以使用依赖注入进行单元测试
【发布时间】:2014-01-10 17:07:37
【问题描述】:

我正在开发一个广泛使用单例模式以及一些全局模式的大型代码库。我刚刚开始尝试编写一些单元测试,但是单例和全局给我带来了很多问题,在阅读之后,依赖注入似乎是要走的路。

进行此更改的重构任务非常艰巨,我正在尝试找出最佳方法。据我了解,基本思想是采取这样的做法:

foo()
{
    GraphicsCache::Instance()->GetMyImage();
    // do stuff
}

把它变成这样的东西:

foo(GraphicsCache *Cache)
{
    Cache->GetMyImage();
    // do stuff
}

这样我就可以模拟这些对象并在我的测试中使用这些模拟。但是有很多这些类型的对象(事件记录器、网络对象、其他缓存等),这几乎意味着我最终将在各处传递大量对象,并最终添加一个到处都是很多代码。我有正确的想法吗?有没有更好的方法来做到这一点?

我的一个想法是拥有一个对象,它是所有这些当前全局对象的容器,我所要做的就是传递一个引用,但这感觉不对,因为大多数地方都不会不需要访问每一个全局对象,只是一个子集。

【问题讨论】:

  • 回复:您关于传递单个对象的想法。您只是在传递一个指向全局对象的指针。其他例程是否需要它们并不重要:您不会传递大量数据:只是一个指针。信息在那里,但他们不必使用它。这就像随身携带一部具有您从未使用过的功能的手机。它们在那里,但您不必使用它们。没有这些功能,手机不会更轻。

标签: c++ unit-testing design-patterns dependency-injection tdd


【解决方案1】:

你应该按照你的建议去做,传递对象是正确的方法。 您可能也想使用工厂。是的,你最终会添加很多代码,但这是值得的。

不要创建要传递的“包”对象,据我所知,它会产生与全局变量完全相同的问题,您无法判断对象真正依赖什么,因为所有内容都在传递给他们。

如果你真的没有那么多时间,或者它可能由于其他原因无法正常工作(比如你的经理对此不满意),你还可以创建专用工厂并更改工厂创建的对象测试。

你可以阅读更多关于它的信息,它被称为Interface Segregation Principle

简单地说,该原则指出一个类不应依赖于它不使用的函数。

【讨论】:

  • 这很有意义,我明白为什么“包”会不好。代码库有点乱,因为单例意味着你可以逃避拒绝面对软件架构问题,而且事后很难理清!
  • 另外,几乎每个类都需要事件记录器,所以这肯定意味着向代码库中的每个类添加事件记录器构造函数参数。这似乎有点疯狂?
  • @MatthewHubble 我们实际上在我的工作场所也遇到了事件记录器问题,我们最终做的是在构造函数中创建事件记录器(当然使用工厂,总线仍然是个坏主意)并且仍然有一些类依赖于全局事件记录器。这可能会有所帮助 - stackoverflow.com/questions/20830673/…。我们找不到解决日志记录问题的简单方法...
【解决方案2】:

我是从 c# 的角度回答这个问题,但它应该同样适用于 C++。对象肯定比单例更好,但对于真正的横切问题(即日志记录等),您可能需要考虑将这些单例修改为 AmbientContext 模式。它基本上是类固醇上的单例,但是您在应用程序/测试启动时将其实例化为您选择的接口实现。这将使记录器完全可测试,同时不会导致您必须将它们注入到所有内容中。

环境上下文也可以作为一个临时的中间地带,允许测试,以防止在运行中重构所有这些单例。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-06
    • 2021-06-19
    • 1970-01-01
    相关资源
    最近更新 更多