澄清上帝对象,测试类型,测试覆盖率,以及如何使类单元可测试

时间:2014-10-11 12:03:58

标签: php unit-testing testing domain-driven-design functional-testing

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

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

无论哪种方式,我都愿意接受更好的设计。

粗糙的类图看起来像这样

enter image description here

World是可疑的上帝阶级。其依赖项ServiceProviderCommandRegistryEventHubEnvironment通过其构造函数注入。

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

  1. 将其依赖项存储在私有字段中
  2. 使用($this, 'onCommandIssued')注册一个钩子eventHub,以便world收到有关所有正在执行的命令的通知,而不是通过world实例本身(world也是方法executeCommand
  3. 告诉环境采用世界:$this->environment->adoptWorld($this)。环境的作用是使世界适应运行环境的某些现实,例如,Web环境具有一些在控制台应用程序环境中不可用的特定服务(例如"会话&#34 ;服务)
  4. 通过活动中心通知世界的建设已完成:$this->eventHub->notify(new WorldConstructedEvent($this));
  5. 也许这看起来像一个沉重的构造函数,但它只是被定义为"构建世界"。

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

    现在问题/问题:

    1. 我正在尝试对这个构造函数进行单元测试,但是我必须添加一堆@uses注释,这使得除了单元测试之外还感觉不到其他任何东西。什么是功能测试?单元测试World很笨拙,测试其他任何东西都是微不足道的,我不会在任何其他测试中看到这个问题,这让我问自己为什么会这样,以及如何改进设计。
    2. World是神的对象吗?它所做的就是聚合其他组件并将呼叫转发给它们。
    3. 如何正确地对World的方法进行单元测试?如果我使用大量的存根并依赖注入它们,它仍然是单元测试吗?
    4. 这是针对域驱动的设计(复杂)应用程序,我愿意接受可以使设计更好(可测试和解耦)的建议。

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

      由于我不知道这次讨论将会导致什么,我可以改进我的问题。

1 个答案:

答案 0 :(得分:0)

我终于通过设置适当的期望并模拟依赖项来对该类进行单元测试:

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


}