Ложный только один метод на заглушках PHPSpec

Итак, я пытаюсь переместить один из своих пакетов в тесты 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?

1

Решение

Теперь это работает, тест зеленый. Но это неправильно … Я что-то упустил или это ограничение 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. Тебе сейчас лучше?

9

Другие решения

Итак, вы идете в ресторан, чтобы поужинать. Вы ожидаете, что вам будет предоставлен выбор еды, из которой вы выберете ту, которая вам действительно интересна, и поесть в конце ночи. Чего вы не ожидаете, так это того, что ресторан также взимать плату за прекрасную пару рядом с вами, заказывая бутылку за бутылкой Chteau Margaux 95. Поэтому, когда вы обнаружите, что вы были взимается плата за их еду тоже, вы, вероятно, захотите немедленно позвонить в этот ресторан и ваш банк, потому что это совсем не хорошо что это случилось без тебя!

Вопрос не почему PhpSpec заставляет вас заглушать методы тебя сейчас не волнует Вопрос в том зачем звонишь методы, которые вас не волнуют сейчас. Если они не соответствуют вашим ожиданиям, PhpSpec просто звонит в ваш банк, потому что это совсем не хорошо что его случилось без тебя!

4

Да, вы можете назвать это «ограничением» 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);
}
3
По вопросам рекламы [email protected]