Заглушки и насмешки в PHPUnit в приложении Zend Framework

Я новичок в Zend Framework и PHPUnit. Я преобразую устаревшее приложение в архитектуру MVC и пытаюсь написать модульные тесты. Я немного знаком с концепциями модульного тестирования, но в целом застрял в заглушках и насмешках. Например, рассмотрим следующее

В действии контроллера, которое я пытаюсь проверить, я передаю идентификатор члена. Затем я инициализирую объект-член, используя идентификатор. Затем я вызываю ряд методов, связанных с объектом-членом, и присваиваю возвращаемые значения объектам представления.

class A extends Zend_Controller_Action {
    public function viewAction() {
        $member = new Member($this->getRequest()-> getParam('id'));

        //perform various calls on the member object 

        $gender = $member->getGender();
        ...

        //assign the return values to the view object

        $this->view->assign('gender',$gender);   
        ...

     }
}

Как мне имитировать переменную $member в моих тестах, чтобы я мог настроить возвращаемые значения методов?

Если мое понимание здесь неверно, я был бы очень признателен за некоторые рекомендации.

Спасибо!


person Satyam    schedule 26.02.2012    source источник
comment
В дополнение к тому, что @markus говорит ниже; по этой причине я прекратил модульное тестирование контроллеров, что вынудило меня перенести в свои модели как можно больше логики. В результате теперь у меня очень тонкие контроллеры и очень толстые модели, которые улучшили мой код и сохранили его СУХИМ.   -  person vascowhite    schedule 26.02.2012
comment
Это то, над чем я пытаюсь работать. В таком случае можно не тестировать контроллеры? Даже то немногое, что есть (я предполагаю, что это просто получение результатов из модели и присвоение их переменным представления).   -  person Satyam    schedule 26.02.2012
comment
Да, хороший момент, бережливые контроллеры в любом случае являются целью! Я включу это в свой ответ.   -  person markus    schedule 26.02.2012


Ответы (2)


Если я вас правильно понял, вы пишете тест для этого действия. В этом случае невозможно имитировать $member, так как новый экземпляр создается внутри метода. Вот почему мы все стремимся разместить как можно больше наших операторов new как можно выше в графе объектов (DI).

Как правило, для проверьте свои контроллеры.

Но на самом деле очень сложно или даже невозможно правильно протестировать контроллеры ZF (имеется в виду с полной изоляцией). Вам лучше протестировать остальную часть вашего приложения, вашу общую библиотеку и т. д.

Другими словами, в логике ZF1 контроллер является центральным местом подключения (после начальной загрузки), где традиционно используется множество операторов new. Очевидно, что это приводит к невозможности тестирования, потому что каждый экземпляр, который создается вместо внедрения, не может быть смоделирован.

Как отметил @vascowhite, в целом хорошо стремиться к бережливым контроллерам. Это означает, что переместите как можно больше логики на слой модели. Это приведет к меньшей избыточности (DRY) и в то же время к лучшей тестируемости.

Но обратите внимание, чтобы не раздувать ваши модели. В какой-то момент вы, вероятно, захотите разложить некоторый код на дополнительные компоненты.

Дополнительная проблема заключается в том, что вы не можете имитировать Front Controller, так как он является синглтоном. Таким образом, у вас действительно не так много вариантов для тестирования такого действия. Единственным вариантом было бы внедрить экземпляр члена или получить его из реестра (тоже не очень хорошая идея).

Итак, учитывая все это, ясно, что вы не можете достичь полной изоляции для своих тестов действия. Но

Однако ZF2 будет намного проще протестировать.

person markus    schedule 26.02.2012
comment
Да, именно об этом я и спрашиваю. Итак, просто чтобы убедиться, что я правильно понял, вы предлагаете мне издеваться над методом getParam() фронт-контроллера, чтобы он возвращал имитируемый объект $member? И спасибо за разъяснение того, что я думал, что это так, но не смог найти много литературы, подтверждающей это. - person Satyam; 26.02.2012
comment
На самом деле, я тоже ошибался, насмехаясь над фронт-контроллером, это синглтон, еще один антипаттерн тестируемости. Смотрите мой измененный ответ. - person markus; 26.02.2012
comment
В какой-то момент я использовал реестр, но отказался от него из-за проблем с тестированием, которые он создавал. Под инъекцией вы имеете в виду, что я должен передать переменную $member в качестве параметра действия? Я не думаю, что это возможно, поскольку мне нужен идентификатор члена из запроса для создания объекта $member. - person Satyam; 26.02.2012
comment
На самом деле, вещи в реестре действительно легко высмеивать. Просто создайте фиктивный объект и сохраните его в реестре. Вы можете внедрить экземпляр класса-члена, а затем использовать getMember($id), чтобы фактически получить член. В любом случае не рекомендуется передавать определенный идентификатор члену через конструктор. - person markus; 26.02.2012
comment
Мне нравится идея создания метода getMember($id). Спасибо за вашу помощь @markus. Я приму ваш ответ и опубликую свое решение на благо других, как только у меня будет возможность попробовать его. - person Satyam; 26.02.2012

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

Для написания функциональных тестов с помощью ZF рассмотрите возможность использования Codeception: http://codeception.com/01-27-2012/bdd-with-zend-framework.html

Также есть несколько примеров, как можно сделать юнит-тесты для контроллера. Но, искренне, я рекомендую юнит-тестировать контроллеры после того, как для них будут сделаны функциональные тесты.

http://codeception.com/docs/06-UnitTestsAndBDD#TestingController

person Davert    schedule 27.02.2012