0

我有这种情况,我想运行 PHPUnit 测试并检查当前测试类的行为,如下所示:

public function it_allows_to_add_items()
{
        // Create prophesies
        $managerProphecy = $this->getProphet(ListingManager::class);
        $listingItemProphecy = $this->getProphet(ListingItemInterface::class);

        $listing = factory(\App\Misc\Listings\Listing::class)->create();
        $manager = new ListingManager($listing);

        $item = factory(\App\Misc\Listings\ListingItem::class)->make(['listing_id' => null]);
        $item2 = factory(\App\Misc\Listings\ListingItem::class)->make(['listing_id' => null]);

        $manager->addItem($item);
        $managerProphecy->validate($listingItemProphecy)->shouldBeCalledTimes(2);
        $manager->addItem($item2);

        $this->assertTrue(true);
    }

这甚至可能吗?

我当然得到

1) GenericListingManagerTest::it_allows_to_add_items
Some predictions failed:
  Double\App\Misc\Listings\ListingManager\P2:
    Expected exactly 2 calls that match:
      Double\App\Misc\Listings\ListingManager\P2->validate(exact(Double\ListingItemInterface\P1:00000000058d2b7a00007feda4ff3b5f Object (
        'objectProphecy' => Prophecy\Prophecy\ObjectProphecy Object (*Prophecy*)
    )))
    but none were made.
4

1 回答 1

1

我认为你处理这个测试的方式有点不对劲。如果我理解正确,您想验证它是否addItem(object $item)正常工作,即管理器包含该项目并且该项目与您添加的相同。为此,您不需要预言,实际上您的测试实际上并没有使用您创建的预言。根据您的经理的样子,您可以编写如下内容:

function test_manager_add_items_stores_item_and_increases_count()
{
    $manager = new ListingManager(); // (2)
    $item = new ListingIem(); // (3)
    $initialCount = $manager->countItems(); // (1)

    $manager->addItem($item);

    $this->assertEquals($initialCount + 1, $manager->countItems());
    // Assuming offset equals (item count - 1) just like in a numeric array
    $this->assertSame($item, $manager->getItemAtOffset($initialCount));
}

(1) 假设您的经理有一个 count() 方法,您可以检查前后计数是否增加了添加的项目数。

(2) 我们想要测试真正的经理——这就是为什么我们在这里不需要预言创建的模拟——我们可以使用一个真实的项目,因为它只是一个值。

(3) 我不确定你为什么有一个ListingItemInterface。ListingItem 是否真的有不同的实现,如果是这样,你真的想要一个可能包含所有这些的通用 ListingManager,还是你需要一个更具体的来确保每个 Manager 只包含它的特定类型的项目?这实际上取决于您的用例,但看起来您可能违反了 SOLID 中的 I(接口隔离原则)或 L(Liskov 替换原则)。

根据您的用例,您可能想要添加真实的项目,例如 2 种不同的类型,以明确表明您可以在其中放置 2 种不同的接口实现,或者您可以像上面那样做,只需添加一个 ListingItem 并验证管理器中的每个项目都实现了接口 - 我把找到那个断言留给你;)。当然,您也可以使用您的工厂来创建项目。重要的是我们测试assertSame()托管对象和我们最初创建的对象是否相同,这意味着引用完全相同的对象。

如果您想确保额外的行为,您可以添加额外的测试,例如限制您可以放入管理器的项目类型或当您将无效对象放入管理器时它的行为方式。

重要的是,您想要测试 Manager 的实际行为,这就是您不想使用模拟的原因。如果你真的需要它,你可以为 ListingItemInterface 使用一个模拟。在这种情况下,测试可能看起来像这样:

function test_manager_add_items_stores_item_and_increases_count()
{
    $manager = new ListingManager();
    $dummyItem = $this->prophecy(ListingIemInterface::class);
    $initialCount = $manager->countItems();

    $manager->addItem($dummyItem->reveal());

    $this->assertEquals($initialCount + 1, $manager->countItems());
    $this->assertSame($dummyItem, $manager->getItemAtOffset($initialCount));
}

编辑:如果addItem检查您要跳过的验证,例如因为您提供的空项目无效并且您不在乎。您可以使用 PHPUnit 自己的模拟框架来部分模拟管理器,如下所示:

$item = new ListingItem();
$managerMock = $this->getMockBuilder(ListManager::class)
    ->setMethods(['validate'])
    ->getMock();
$managerMock
    ->expects($this->exactly(2))
    ->method('validate')
    ->with($this-> identicalTo($item))
    ->willReturn(true);

$managerMock->addItem($item);
$managerMock->addItem($item);

你不必在最后断言任何东西,因为expects()它已经断言了一些东西。您的 Manager 将正常工作,除了validate(),这意味着您将在 addItem() 中运行代码,并且如果在那里调用 validate(每个项目一次),则测试将通过。

于 2017-01-01T12:00:56.527 回答