1

好的,所以我试图将我的一个包移到 PHPSpec 测试中,但很快我遇到了这个问题。packages 是一个购物车包,所以我想测试一下,当您将两个商品添加到购物车时,购物车的计数为 2,很简单。但是当然,在购物车中,当添加两个相同的商品时,购物车中不会有新条目,但原始商品的“数量”将为 2。不同的尺寸。因此,每个项目都由一个唯一的 rowId 标识,基于它的 ID 和选项。

这是生成 rowId 的代码(由add()方法使用):

protected function generateRowId(CartItem $item)
{
    return md5($item->getId() . serialize($item->getOptions()));
}

现在我已经这样写了我的测试:

public function it_can_add_multiple_instances_of_a_cart_item(CartItem $cartItem1, CartItem $cartItem2)
{
    $this->add($cartItem1);
    $this->add($cartItem2);

    $this->shouldHaveCount(2);
}

但问题是,两个存根都返回nullgetId()方法。所以我尝试willReturn()为那个方法设置,所以我的测试变成了这样:

public function it_can_add_multiple_instances_of_a_cart_item(CartItem $cartItem1, CartItem $cartItem2)
{
    $cartItem1->getId()->willReturn(1);
    $cartItem2->getId()->willReturn(2);

    $this->add($cartItem1);
    $this->add($cartItem2);

    $this->shouldHaveCount(2);
}

但是现在我收到错误,告诉我意外的方法被称为getName(). 所以我必须对 CartItem 接口上调用的所有方法执行相同的操作:

public function it_can_add_multiple_instances_of_a_cart_item(CartItem $cartItem1, CartItem $cartItem2)
{
    $cartItem1->getId()->willReturn(1);
    $cartItem1->getName()->willReturn(null);
    $cartItem1->getPrice()->willReturn(null);
    $cartItem1->getOptions()->willReturn([]);

    $cartItem2->getId()->willReturn(2);
    $cartItem2->getName()->willReturn(null);
    $cartItem2->getPrice()->willReturn(null);
    $cartItem2->getOptions()->willReturn([]);

    $this->add($cartItem1);
    $this->add($cartItem2);

    $this->shouldHaveCount(2);
}

现在这有效,测试是绿色的。但感觉不对...我错过了什么还是对 PHPSpec 的限制?

4

3 回答 3

9

现在这有效,测试是绿色的。但感觉不对...我错过了什么还是对 PHPSpec 的限制?

我认为在这种情况下感觉不对是好的,因为它应该。正如上面提到的@l3l0,PHPSpec 是一个设计工具,它在这里为您提供了关于您的设计的清晰信息。

你所挣扎的是你Cart违反了单一责任原则的事实——它做的不止一件事——它管理CartItems并知道如何从中产生RowId。因为 PHPSpec 强制你对它的整个行为进行存根,CartItem所以它会给你一条消息来重构生成RowId.

现在假设您将 RowIdGenerator 提取到单独的类中(此处未介绍它自己的规范):

class RowIdGenerator
{
    public function fromCartItem(CartItem $item)
    {
        return md5($item->getId() . serialize($item->getOptions()));
    }
}

然后你通过构造函数注入这个生成器作为你的购物车的依赖项:

class Cart
{
    private $rowIdGenerator;

    public function __construct(RowIdGenerator $rowIdGenerator)
    {
        $this->rowIdGenerator = $rowIdGenerator;
    }
}

然后您的最终规格可能如下所示:

function let(RowIdGenerator $rowIdGenerator)
{
    $this->beConstructedWith($rowIdGenerator);
}

public function it_can_add_multiple_instances_of_a_cart_item(RowIdGenerator $rowIdGenerator, CartItem $cartItem1, CartItem $cartItem2)
{
    $rowIdGenerator->fromCartItem($cartItem1)->willReturn('abc');
    $rowIdGenerator->fromCartItem($cartItem1)->willReturn('def');

    $this->add($cartItem1);
    $this->add($cartItem2);

    $this->shouldHaveCount(2);
}

并且因为您模拟了 id 生成器的行为(并且您知道必须进行这种通信),所以现在您符合 SRP。你现在感觉好些了吗?

于 2014-12-10T19:55:06.400 回答
4

所以你走进一家餐馆是为了吃晚饭。您希望您可以选择一顿饭,从中选择您今天真正感兴趣的一顿饭,并在晚上结束时收取费用。您没想到的是,餐厅还会您旁边的一对可爱的夫妇点一瓶一瓶的 Chteau Margaux 95 向您收费。因此,当您发现自己也被收取了他们的餐费时,您可能会想立即打电话给那家餐馆和你的银行,因为这完全不是你所期望的!

问题不在于为什么 PhpSpec 会强迫您存根您现在不关心的方法。问题是为什么你现在调用你不关心的方法。如果它们不符合您的期望,PhpSpec 只会为您致电您的银行,因为在您没有预料到的情况下发生这种情况是完全不行的!

于 2014-12-10T21:39:23.860 回答
3

是的,您可以称其为 phpspec 的“限制”。基本上 phpspec 是严格的 TDD 和对象通信设计工具 IMO。

您会看到将 $cartItem 添加到集合中的效果比您预期的要多得多。

第一个您不必使用存根(如果您不关心内部对象通信)示例:

function it_adds_multiple_instances_of_a_cart_item()
{
    $this->add(new CartItem($id = 1, $options = ['size' => 1]));
    $this->add(new CartItem($id = 2, $options = ['size' => 2]));

    $this->shouldHaveCount(2);
}

function it_adds_two_same_items_with_different_sizes()
{
    $this->add(new CartItem($id = 1, $options = ['size' => 1]));
    $this->add(new CartItem($id = 1, $options = ['size' => 2]));

    $this->shouldHaveCount(2);   
}

function it_does_not_add_same_items()
{
    $this->add(new CartItem($id = 1, $options = []));
    $this->add(new CartItem($id = 1, $options = []));

    $this->shouldHaveCount(1);   
}

你也可以用其他方式。从通信的角度来看,多次查询相同的对象实例并不是那么有效。许多公共方法意味着许多不同的组合。您可以计划沟通并执行以下操作:

function it_adds_multiple_instances_of_a_cart_item(CartItem $cartItem1, CartItem $cartItem2)
{
   $this->add($cartItem1);
   $cartItem1->isSameAs($cartItem2)->willReturn(false);
   $this->add($cartItem2);

   $this->shouldHaveCount(2);
}

function it_does_not_add_same_items((CartItem $cartItem1, CartItem $cartItem2)
{
    $this->add($cartItem1);
    $cartItem1->isSameAs($cartItem2)->willReturn(true);
    $this->add($cartItem2);

    $this->shouldHaveCount(1);   
}
于 2014-12-07T21:18:07.610 回答