DI, PHPUnit и extract

от автора


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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *