DI, PHPUnit и extract
Инверсия зависимостей (Dependency Injection) — весьма приятная вещь, во многом облегчающая жизнь разработчику. Но она же и является причиной появления таких вот конструкторов:
public function __construct( \Psr\Log\LoggerInterface $logger, \Zend_Db_Adapter_Pdo_Abstract $dba, ISomeService $service, ... ) { $this->_logger = $logger; $this->_dba = $dba; $this->_service = $service; ... }
Использование extract()
в unit-тестах может существенно облегчить жизнь, если нужно несколько раз создать один и тот же набор mock’ов для тестирования различных особенностей реализации разрабатываемого класса.
Допустим, у нас есть класс с указанным выше конструктором. Для мокирования окружения в отдельном тестовом методе нужно написать что-то такое:
/* create mocks */ $mLogger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); $mDba = $this->getMockBuilder('Zend_Db_Adapter_Pdo_Abstract')->getMockForAbstractClass(); $mService = $this->getMockBuilder('Vendor\Module\ISomeService')->disableOriginalConstructor()->getMock(); ... /* setup mocks behaviour */ ... /* */ $obj = new Demo($mLogger, $mDba, $mService, ...); $res = $obj->method($arg1, ...); $this->assert...
Если количество зависимостей в объекте достаточно высоко, а реализуемый им функционал довольно сложен, то unit-тест может содержать изрядное количество блоков с инициализацией mock-объектов, поведение которых затем специализируется в соответствии с проверяемыми требованиями. А если изменилось количество зависимостей в конструкторе, то приходится добавлять новые mock-объекты в каждый тестовый метод и переделывать каждый $obj = new Demo(...);
.
Следуя принципу DRY (Don’t Repeat Yourself), следует сосредосточить создание моков в одном месте, а затем уже специализировать их поведение в зависимости от условий тестирования в соответствующем тестовом методе. Это можно сделать при помощи функции extract
. Сначала создаем в PHPUnit’е метод, который инициализирует mock’и и сам тестируемый объект, возвращая все богатство в виде ассоциативного массива:
private function _prepareMocks() { $mLogger = $this->getMockBuilder('Psr\Log\LoggerInterface')->getMock(); $mDba = $this->getMockBuilder('Zend_Db_Adapter_Pdo_Abstract')->getMockForAbstractClass(); $mService = $this->getMockBuilder('Vendor\Module\ISomeService')->disableOriginalConstructor()->getMock(); ... $obj = new Demo($mLogger, $mDba, $mService, ...); $result = [ 'mLogger' => $mLogger, 'mDba' => $mDba, 'mService' => $mService, ... 'obj' => $obj ]; return $result; }
а затем создаем нужные нам переменные в нужной нам тестовой функции и специализируем поведение нужных нам mock’ов:
public function test_method() { /* create mocks */ extract($this->_prepareMocks()); /* setup mocks behaviour */ // ... $res = $obj->method(); }
При желании можно проанонсировать создаваемые переменные и пользоваться всеми преимуществами autocomple’а в IDE, заплатив за это необходимостью поддерживать анонсы переменных в актуальном состоянии при изменении количества mock’ируемых зависимостей:
/* create mocks */ /** * @var $mLogger \PHPUnit_Framework_MockObject_MockObject * @var $mDba \PHPUnit_Framework_MockObject_MockObject * @var $mService \PHPUnit_Framework_MockObject_MockObject * ... * @var $obj Demo */ extract($this->_prepareMocks());
ссылка на оригинал статьи https://habrahabr.ru/post/277867/
Добавить комментарий