提问者:小点点

明确神对象、测试类型、测试覆盖率,以及如何让一个类可单元测试


我目前正在尝试做的是在单元测试中测试类的构造函数。

我不确定这个对象的实例是否是“上帝对象”,我会说不是,因为它只聚合了几个其他组件。

无论哪种方式,我都对更好的设计持开放态度。

粗略的类图看起来像这样

World是可疑的神类。它的依赖项ServiceProviderCommand d注册表EventHub环境是通过它的构造函数注入的。

在其构造函数中,World执行以下操作:

  1. 将其依赖项存储在私有字段中
  2. eventHub注册一个钩子($this,'onCommand dIsted'),以便world接收有关所有正在执行的命令的通知,而不是通过world实例本身(world也有一个方法执行命令
  3. 告诉环境采纳世界:$this-

也许这看起来像一个沉重的构造函数,但它只是被定义为“构建世界”。

World基本上是发送命令的网关(作为数据传输对象,通过World::executeCommand()),不同的服务可以向其注册挂钩。

现在到问题/问题:

  1. 我正在尝试对这个构造函数进行单元测试,但是我必须添加一堆@使用注释,这使得它感觉就像除了单元测试之外的其他任何东西。那么它是什么,功能测试?单元测试World很尴尬,测试其他任何东西都非常琐碎,我在任何其他测试中都没有看到这个问题出现,这让我问自己为什么会这样,以及如何改进设计。
  2. World是上帝对象吗?它所做的只是聚合其他组件并将调用转发给它们。
  3. 如何正确地对World的方法进行单元测试?如果我使用大量存根并依赖注入它们,它仍然是单元测试吗?

这适用于领域驱动的设计(复杂)应用程序,我愿意接受使设计更好(可测试和解耦)的建议。

如果您需要更多详细信息,请在评论中告诉我。

由于我不知道这次讨论会导致什么,我可能会细化我的问题。


共1个答案

匿名用户

我终于通过设置正确的期望和模拟依赖关系来对类进行单元测试:

<?php

namespace Common\World;

use TestFramework\TestCase;

class WorldTest extends TestCase
{
    /**
     * @test
     * @covers \Common\World\World::__construct
     * @uses   \Common\World\World::setEventHub
     * @uses   \Common\World\Event\Adopted
     */
    public function construction()
    {
        /** @var \Common\World\Environment $environmentStub |\PHPUnit_Framework_MockObject_MockObject */
        $environmentStub = $this->getMockBuilder('Common\World\Environment')->getMock();
        /** @var \Common\World\EventHub|\PHPUnit_Framework_MockObject_MockObject $eventHubStub */
        $eventHubStub = $this->getMock('Common\World\EventHub');

        $environmentStub->expects($this->once())
            ->method('adoptWorld')
            ->will($this->returnCallback(function (World $world) use ($eventHubStub) {
                $world->setEventHub($eventHubStub);
                return true;
            }));
        $eventHubStub->expects($this->once())
            ->method('trigger')
            ->with($this->isInstanceOf('Common\World\Event\Adopted'));
        $this->assertInstanceOf('Common\World\World', new World($environmentStub));
    }

    /**
     * @test
     * @covers \Common\World\World::__construct
     * @expectedException \RuntimeException
     * @expectedExceptionMessage the environment has rejected this world for incompatibility reasons
     */
    public function construction_rejected()
    {
        /** @var \Common\World\Environment $environmentStub */
        $environmentStub = $this->getMockBuilder('Common\World\Environment')->getMock();
        $environmentStub->expects($this->once())
            ->method('adoptWorld')
            ->will($this->returnValue(false));

        new World($environmentStub);
    }


}