Тестирование вызова нескольких методов в phpspec

В прошлом я всегда сталкивался с определенной проблемой с phpspec:

Предположим, у меня есть метод, который вызывает несколько методов для другого объекта

class Caller {
    public function call(){
       $this->receiver->method1();
       ...
       $this->receiver->method2();
    }
}

В BDD я бы сначала написал тест, который гарантирует, что будет вызван method1.

function it_calls_method1_of_receiver(Receiver $receiver){
    $receiver->method1()->shouldBeCalled();
    $this->call();
}

И тогда я напишу следующий тест, чтобы гарантировать, что будет вызван method2.

function it_calls_method2_of_receiver(Receiver $receiver){
    $receiver->method2()->shouldBeCalled();
    $this->call();
}

Но этот тест не проходит в phpspec, потому что method1 вызывается перед method2. Чтобы удовлетворить phpspec, я должен проверить оба вызова метода.

 function it_calls_method2_of_receiver(Receiver $receiver){
    $receiver->method1()->shouldBeCalled();
    $receiver->method2()->shouldBeCalled();
    $this->call();
}

Моя проблема с этим в том, что он раздувает каждый тест. В этом примере это всего лишь одна дополнительная строка, но представьте метод, который создает объект с множеством сеттеров. Мне нужно было бы написать все сеттеры для каждого теста. Было бы довольно трудно понять цель теста, так как каждый тест большой и выглядит одинаково.

Я совершенно уверен, что это не проблема с phpspec или bdd, а скорее проблема с моей архитектурой. Каков был бы лучший (более проверяемый) способ написать это?

Например:

public function handleRequest($request, $endpoint){
    $endpoint->setRequest($request);
    $endpoint->validate();
    $endpoint->handle();
}

Здесь я проверяю, предоставляет ли запрос всю необходимую информацию для конкретной конечной точки (или выдает исключение), а затем обрабатывает запрос. Я выбираю этот шаблон, чтобы отделить проверку от логики конечной точки.

1 ответ

Решение

Пророчество, фреймворк, используемый PhpSpec, очень самоуверенно. Это следует за модернистским подходом (Лондонская школа TDD), который защищает то, что мы должны описывать одно поведение за один раз.

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

Скажем, вы решили пойти дальше и описать свой код без рефакторинга. Если вы заинтересованы во втором вызове, в соответствии с вашим примером, вы должны заглушить другие вызовы, используя willReturn, или похожие. Например $endpoint->setRequest(Argument::type(Request::class))->willReturn() вместо shouldBeCalled(),

Другие вопросы по тегам