Итак, я пытаюсь переместить один из своих пакетов в тесты PHPSpec, но вскоре столкнулся с этой проблемой.
Пакеты — это пакет с корзиной покупок, поэтому я хочу проверить, что при добавлении двух товаров в корзину, в корзине просто два счета.
Но, конечно, в корзине покупок при добавлении двух одинаковых товаров в корзине не будет новой записи, но исходный товар получит «кол-во», равное 2. Так, но не тогда, когда они, например, различные размеры.
Таким образом, каждый элемент идентифицируется уникальным rowId, основанным на его идентификаторе и опциях.
Это код, который генерирует 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);
}
И поскольку вы издевались над поведением генератора идентификаторов (и вы знаете, что это общение должно происходить), теперь вы соответствуете SRP. Тебе сейчас лучше?
Итак, вы идете в ресторан, чтобы поужинать. Вы ожидаете, что вам будет предоставлен выбор еды, из которой вы выберете ту, которая вам действительно интересна, и поесть в конце ночи. Чего вы не ожидаете, так это того, что ресторан также взимать плату за прекрасную пару рядом с вами, заказывая бутылку за бутылкой Chteau Margaux 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);
}