2014-12-03 2 views
1

Итак, я пытаюсь перенести один из моих пакетов на тесты PHPSpec, но вскоре я столкнулся с этой проблемой. Пакеты - пакет для покупок, поэтому я хочу проверить, что, когда вы добавляете два предмета в корзину, тележка имеет счет двух, простых. Но, конечно, в корзине покупок при добавлении двух одинаковых предметов в корзине не будет новой записи, но исходный элемент получит «qty» из 2. Так что, но не тогда, когда они есть, например , различные размеры. Таким образом, каждый элемент идентифицируется уникальным rowId на основе его идентификатора и опций.Откажитесь только от одного метода на заглушки PHPSpec

Это код, который генерирует 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?

ответ

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); 
} 
+0

Интересный ответ, я уже «боялся», это было ограничение. Меня действительно не интересует внутренняя коммуникация объектов. Но причина, по которой мне нужны заглушки, - это то, что CartItem не является классом, а интерфейсом. Я хочу, чтобы люди могли использовать любой класс, который они хотят, до тех пор, пока они реализуют этот интерфейс. Поэтому я не могу просто создать экземпляр 'new CartItem()'. – Crinsane

+0

Наличие геттеров и сеттеров и/или сумматоров в одном интерфейсе - не очень крутая идея;) Возможно, вы можете упростить интерфейс и сделать меньше методов. Вас может заинтересовать принцип разделения интерфейса. – l3l0

+0

Как бы я это ни делал, я имею в виду, что все, о чем я забочусь, это то, что какой бы предмет, который кто-то пытается помещать в корзину, имеет имя, которое я могу получить, ID, который я могу получить, и т. Д. Поэтому мне нужны эти горстка геттеров ... Может быть, я вижу это неправильно, но для меня это имеет смысл. Мне все равно, какой тип объекта он есть, пока я могу получить идентификатор, имя, цену и параметры. :) – Crinsane

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. Тебе сейчас лучше?

4

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

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

Смежные вопросы