Есть пара весомых поводов не использовать рекурсию, но это не повод не использовать рекурсию вообще. Программы, во-первых, создаются программистами для программистов, и лишь во-вторых — программистами для компьютеров. В итоге, некоторыми годными программами могут пользоваться неподготовленные люди. Рекурсия имеет одно безусловное преимущество перед итерацией — читабельность. Когда программист создает программы для себе подобных, рекурсия имеет право на существование до тех пор, пока не докажет обратного (т.е. — не будет запущена на компьютере и не поперхнется реальными данными).
Тестирование — это, по сути, создание программ для программ, позволяющее программистам отодвигать порог непреодолимой сложности в разрабатываемых приложениях. Столкнувшись на днях с необходимостью написать юнит-тест для рекурсивного метода я был неприятно удивлен необходимостью мокировать сам тестируемый метод. Альтернатива — создавать такие входные данные, которые бы позволяли протестировать все ветки рекурсии в одном тестовом методе. В перспективе вырисовывалось не снижение сложности, а наоборот — ее увеличение. Порывшись в интернетах, я обнаружил кучу информации о том, чем нехороша рекурсия, массу советов, как перейти от рекурсии к итерации, но так и не нашел на русских формах того, что искал — как тестировать рекурсивный метод. Решив, что подготовить тестовые данные для трех проходов по коду — не такая уж непреодолимая сложность, отложил эту задачу до утра. Под катом решение, пришедшее в голову за ночь, позволяющее разбивать тестирование рекурсивных методов на части.
Рекурсивный метод
public function foo($arg1, $arg2)
{
//...
$out = $this->foo($in1, $in2);
//...
}
Рекурсивный метод с оберткой
Создаем обертку для метода и делаем так, чтобы сам метод вызывал только обертку, а обертка вызывала метод:
public function foo($arg1, $arg2)
{
//...
$out = $this->fooRecursive($in1, $in2);
//...
}
public function fooRecursive($arg1, $arg2)
{
return $this->foo($arg1, $arg2);
}
Мокирование "обертки"
public function test_foo()
{
/* create mock for wrapper 'fooRecursive'*/
$obj = \Mockery::mock(\FooClass::class . '[fooRecursive]');
$obj->shouldReceive('fooRecursive')->once()
->andReturn('out');
/* call recursive method 'foo' */
$res = $obj->foo('arg1', 'arg2');
}
Да, решение весьма незамысловатое, но, может кому-нибудь пригодится, и ему удастся потратить свою ночь на что-то более полезное.