使用依赖注入进行单元测试

3
我有一个关于依赖注入的问题。到目前为止,我一直保持简单的方法,即将对象创建从对象中分离出来,并在构造函数中传递它们。我已经达到了需要多个对象的大型类的点。有些甚至有包含其他对象的对象,其中有许多单例模式。当测试这些类时,情况会很快变得丑陋,因为它们远非“隔离”,仍然被硬编码到其依赖项中。
因此,对于一个简单的类注入一个或两个对象是直截了当的,
我研究了依赖容器,看到了许多实现,现在想知道使用容器与使用注册表等相比的优势。一个人是否可以轻松地使用注册表来保存匿名函数,在调用时创建所需的依赖项?
我窥视了两个容器, Php Dependency Pimple在实现上有很大的不同。
我想知道使用容器与直接传递对象的优势。我不明白php-dependency的实现如何进行测试,即在phpunit中如何实现模拟数据库对象而不注入实际类?将依赖关系映射并在doctags中使用是否有优势?
Class Book {

  private $_database;

  /**
   * @PdInject database
   */
  public function setDatabase($database) {
      $this->_database = $database;
  }

}

另一方面,Pimple采用了完全不同的方法。没有docblock标签,没有在单独文件中进行映射,它看起来像是一种强化版的注册表....

  Objects are defined by anonymous functions that return an instance of the object:

 // define some parameters
 $container['cookie_name'] = 'SESSION_ID';
 $container['session_storage_class'] = 'SessionStorage';

...同时可以作为工厂运行的代码:

$container['session'] = function ($c) {
    return new Session($c['session_storage']);
};

声明共享资源始终使用同一实例(单例?):
$c['session'] = $c->share(function ($c) {
return new Session($c['session_storage']);
});

这是我得到使用一个简单的注册表来保存对象或匿名函数的想法的地方。但是,我在这种方法中漏掉了什么吗?对于Pimple,我可以看到如何进行测试,但从测试角度来看,Php-Dependency对我来说不清楚。
1个回答

2

通常在我们的应用程序中,我们进行构造函数注入,并为系统中的所有组件定义一个接口:

class Book
{
    /**
     * @var Db_AdapterInterface
     */
    private $_database;

    public function __construct(Db_AdapterInterface $database)
    {
        $this->_database = $database;
    }
}

我们有一个标准的Db_Adapter,还有一个名为Db_TestAdapter的适配器。在Db_TestAdapter中,我们可以定义测试中SQL查询的结果。
对于我们的正常应用程序,我们的容器大致如下:
$container->add('Db_AdapterInterface', new Db_Adapter());

在我们的测试中,我们使用了这行代码:

$container->add('Db_AdapterInterface', new Db_TestAdapter());

要获取 Book 的实例,我们只需向容器请求即可:

$book = $container->construct('Book');

容器将所有所需的依赖项注入对象。

如果您保持所有对象之间松散耦合,即对象A仅需要接口B,那么您始终可以为对象A提供测试实现。此时使用哪个容器并不重要。

我们有一个非常简单的IoC容器,可以进行基本的构造函数注入。我们的测试从填充标准测试对象的基类继承。这样,我们就不需要太多代码来构造我们想要测试的对象。

更新: 我添加了一个在容器中连接事物的示例。


我认为这就是stefgosselin所在的位置,并且想知道如何选择DI策略。 - David Harkness
当你运行测试时,@var Db_AdapterInterface 是如何被模拟覆盖的?我现在明白了,只是想确认一下我的理解是否正确:在测试时,您的组件具有模拟/存根等价物,并加载 DI 容器并将其交给调用测试? - Stephane Gosselin
问题在于澄清采用这种方法与加载对象注册表并像新的$book = Book($registry->get('Db_Adapter'))一样传递对象之间的区别。看起来有些容器只是充当注册表。 - Stephane Gosselin
谢谢,现在明白了 -> 我很感激。 - Stephane Gosselin
你如何对构造函数的签名进行反思,以便注入正确的依赖项?你会在测试类中实现这个吗? - Dmitry Minkovsky

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接