Тестирование вызова нескольких методов в 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()
,