Наконец-то запилили плагин для PHPStorm, который вскрывает симфонийский контейнер и рисует подсказки для объектов, возвращающихся из $container->get('')
Волшебный бандлоподключатель вот еще сделали — толку, правда, немного, все равно конфиги-то править надо, плюс еще головняки с приоритетами рутов. Но штука давно напрашивалась.
Прицепили Bower через бандл, тонкостей не смотрел, но — радует. И мне как-то пофиг, я скрипт на Grunt запилил для сборки статики, и верстка не сильно заботит.
Я конечно слоупок, но доктриновские расширения тоже давно хотели быть завернутыми в примеси. Вот, сделали наконец.
Наконец-то, человеческий легковесный дата-маппер, пусть и немного страшненький. Для некрупных проектов, где лезть через DBAL стыдно, а Доктрину цеплять лень — самое то.
Поскольку я тут продолжаю писать про тестирование, не могу не упомянуть о прекрасной библиотеке Mockery. Это отличная реализация мок-объектов, которой в разы удобнее пользоваться, в сравнении со средствами PHPUnit, по крайней мере.
Чтобы получить Mockery в проект, надо в блок require в composer.json вписать вот такую вот строчку:
123
"require":{"mockery/mockery":"dev-master"}
Сами авторы пока что советуют не привязываться к конкретной версии, хотя теги в репозитории есть (на момент написания заметки самая свежая версия — 0.7.2). Очевидно, проект еще не очень стабилен, но в любом случае очень удобен. Используется он например вот так:
В приведенном блоке кода я использую весь тот функционал Mockery, который в приципе необходим в большинстве тестов.
Обратите внимание, какой симпатичный (и человекопонятный!) у Mockery API: все методы называются так, как они и должны называться, все нужное выведено в статические функции у легкодоступного класса, словом — сказка, а не моки.
Собственно, что к чему в коде.
Метод mock('\Имя\Класса\Как\Строка') дает нам мок-объект, над которым мы впоследствии можем всячески издеваться. shouldReceive('имяМетода') добавляет в мок ожидание и возвращает его в стиле fluent interface. Этот самый fluent interface позволяет настроить ожидание.
Метод ожидания with() принимает как точные значения параметров, так и Matcher. Matcher по сути является вот таким куском кода:
Mockery\Matcher\Type
1234567891011121314151617181920212223
<?phpnamespaceMockery\Matcher;classTypeextendsMatcherAbstract{/** * Check if the actual value matches the expected. * * @param mixed $actual * @return bool */publicfunctionmatch(&$actual){$function='is_'.strtolower($this->_expected);if(function_exists($function)){return$function($actual);}elseif(is_string($this->_expected)&&(class_exists($this->_expected)||interface_exists($this->_expected))){return$actualinstanceof$this->_expected;}returnfalse;}
Видим, что путем нехитрого колдовства тут проверяется входящее значение, и оно может принадлежать либо к одному из стандартных типов (тех, которые проверяются методами is_bool, is_string и т.д.), либо являться экземпляром класса или экземпляром реализации какого-либо интерфейса, либо вовсе быть функцией.
Также в создании $geocoderMock я после вызова shouldReceive() указываю, что метод using в имитируемом классе должен быть вызван один раз. И делаю это простым вызовом \Mockery\Expectation::once(), что несомненно куда как приятнее, чем стиль PHPUnit. А вот кстати полный набор методов для указания количества вызовов.
С методом andReturn(), думаю, все понятно — он принимает очередь параметров. В моем случае очередь состоит всегда из одного параметра, потому что тестируется достаточно простой функционал, не требующий особых танцев с бубном.
Ну и небольшой нюанс в том, что после объявления ожиданий мока последним значением, вернувшимся в нашем fluent interface, является ожидание, а не сам мок, поэтому в конце вызова нужно вызвать еще и метод \Mockery\Expectation::getMock().
Конечно, это ваше дело, чем пользоваться — Mockery или средствами, встроенными в PHPUnit, но все же мне кажется, что эта библиотека более чем достойна упоминания.
Сегодня задался вопросом о том, почему мой проект на Symfony 2 все еще целится в PHP 5.3 как основную версию. Ну и предположил, что, вероятно, dev-часть можно смело переводить на 5.4 и использовать в тестах все специфичные для новой версии плюшки.
Начал я с трейтов, и нашел для них пока что как минимум одно хорошее применение: тесты.
Поскольку приложение симфонийское, постоянно приходится при запуске тестов этот самый контейнер доставать из static::createClient. Этот момент я реализовал в виде вот такой примеси:
123456789101112131415161718192021222324252627
<?php/** * WebTestCase client-aware test case */traitClientAware{/** * @var \Symfony\Bundle\FrameworkBundle\Client */private$client;/** * Sets up client */publicfunctionsetUp(){$this->client=static::createClient();}/** * @return \Symfony\Component\DependencyInjection\ContainerInterface */publicfunctiongetContainer(){return$this->client->getContainer();}}
Как можно заметить, я не стал здесь отступать от использования метода setUp(), чтобы PHPUnit меня правильно понял, и мне не пришлось в каждом тесте городить свой setUp() с блекджеком и вызовом метода вроде createClient(). Ну и поскольку контейнер тоже приходится получать довольно часто, я вывел еще и шорткат-метод для этой самой цели.
А еще почти что во всех тестах, связанных с моделями, приходится очищать БД в методе setUp(). Однако это довольно-таки много кода, и остается либо вынести всю логику в класс-наследник WebTestCase, или копипастить код. Меня такое решение не устроило, и я решил, что тут не помешает трейт:
Тут надо обратить внимание на строчки 9-11. В них конструкция, которая позволяет одной примеси включить в себя другую в случае, когда их имена методов совпадают. И поскольку в PurgeDb мне однозначно нужен контейнер, я вызываю метод ClientAware::setUp().
Дальше все достаточно очевидно: поскольку контейнер у меня уже есть, я могу смело достать из него Entity Manager и скормить его в Purger, который затем чистит мою БД.
А теперь, собственно, как все это использовать? Вот тест, которому перед запуском нужна пустая база:
Как можно заметить, метод setUp() в нем вообще отсутствует. А поскольку PurgeDb не только чистит БД, но и вызывает setUp() из ClientAware, в моем тесте я могу всегда получить контейнер легко и непринужденно.