【问题标题】:Clarification on god objects, test type, test coverage, and how to make a class unit-testable阐明上帝对象、测试类型、测试覆盖率以及如何使类可单元测试
【发布时间】:2014-10-11 12:03:57
【问题描述】:

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

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

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

所以粗略的类图是这样的

World是疑似神级。它的依赖项ServiceProviderCommandRegistryEventHubEnvironment 通过其构造函数注入。

在其构造函数中,World 做了以下事情:

  1. 将其依赖项存储在私有字段中
  2. eventHub 注册一个钩子($this, 'onCommandIssued'),以便world 接收有关不通过world 实例本身执行的所有命令的通知(world 还有一个方法executeCommand
  3. 告诉环境采用这个世界:$this->environment->adoptWorld($this)。环境的作用是使世界适应运行环境的某些现实,例如,Web 环境具有一些在控制台应用程序环境中不可用的特定服务(例如“会话”服务)
  4. 通过事件中心通知世界构建完成:$this->eventHub->notify(new WorldConstructedEvent($this));

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

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

现在是问题/疑问:

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

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

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

由于我不知道这次讨论会引向何方,我可能会改进我的问题。

【问题讨论】:

  • 我认为您将提供一个模拟 EventHub 并检查是否调用了钩子方法。根据需要对其他依赖项重复此操作。我怀疑我在你的问题中遗漏了一些东西。
  • 你的意思是一个存根EventHub,对吧?测试的类是World,所以这将是我的唯一模拟。
  • 无论如何谢谢,这是个好主意。
  • 模拟和存根不一样:martinfowler.com/articles/mocksArentStubs.html。为了测试您的构造函数,我们关心的是使用预期的参数调用 EventHub 和 Environment 方法。作为一个 Mockest Tester(使用 Fowler 的术语),我会模拟依赖项。你可能更像是一个经典的测试人员。
  • AFAIK 模拟是测试断言的对象,而存根是您在 SUT 中注入的“模拟”,SUT 依赖于它(“依赖注入”)。

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


【解决方案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);
    }


}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-03-13
    • 2018-10-18
    • 1970-01-01
    • 1970-01-01
    • 2017-10-07
    • 1970-01-01
    • 2011-08-04
    相关资源
    最近更新 更多