在 PHPSpec 存根上只模拟一种方法


Mock only one method on PHPSpec stubs

好的,所以我正在尝试将我的一个包移动到 PHPSpec 测试中,但很快我就遇到了这个问题。包裹是购物车包,所以我想测试一下,当您向购物车添加两件商品时,购物车的计数为两,很简单。但是,当然,在购物车中,当添加两个相同的项目时,购物车中不会有新条目,但原始项目将获得"数量"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);
}

但问题是,两个存根都返回null getId() 方法。所以我尝试为该方法设置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 的限制?

现在这行得通,测试是绿色的。但感觉不对劲...我错过了什么还是这是对 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。你现在感觉好些了吗?

所以你走进一家餐馆是为了吃晚饭。你希望你能选择一顿饭,从中选择你今天真正感兴趣的一顿饭,并在晚上结束时被收取费用。您不希望的是,餐厅还会向您旁边的可爱夫妇收取费用,订购一瓶又一瓶的玛歌酒庄95。因此,当您发现也被收取了他们的餐费时,您可能会想立即致电该餐厅和您的银行,因为这完全不行,这在您没有预料到的情况下发生!

问题不在于为什么 PhpSpec 强迫你存根你现在不关心的方法。问题是你为什么称你现在不关心的方法。如果它们不是您期望的一部分,PhpSpec 只会为您致电您的银行,因为这完全不行,他在您意想不到的情况下发生!

是的,你可以称之为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);   
}
相关文章: