PHP
February 2016 24

DI, PHPUnit и setUp

Инверсия зависимостей (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;
    ...
}

Использование setUp() в unit-тестах может существенно облегчить жизнь, если нужно несколько раз создать один и тот же набор mock'ов для тестирования различных особенностей реализации разрабатываемого класса.

Допустим, у нас есть класс с указанным выше конструктором. Для мокирования окружения в отдельном тестовом методе нужно написать что-то такое:

    /* create mocks */
    $mLogger = $this->getMockBuilder(\Psr\Log\LoggerInterface::class)->getMock();
    $mDba = $this->getMockBuilder(\Zend_Db_Adapter_Pdo_Abstract::class)->getMockForAbstractClass();
    $mService = $this->getMockBuilder(\Vendor\Module\ISomeService::class)->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), следует сосредосточить создание mock'ов в одном месте, а затем уже специализировать их поведение в зависимости от условий тестирования в соответствующем тестовом методе. Это можно сделать при помощи функции setUp. Сначала создаем в PHPUnit'е свойства для самого тестируемого объекта и mock'ов зависимостей:

private $mLogger;
private $mDba;
private $mService;
private $obj

а затем прописываем в функции setUp, вызываемую перед каждым тестовым методом, ре-инициализацию mock'ов и объектов:

private function setUp() {
    $this->mLogger = $this->getMockBuilder(\Psr\Log\LoggerInterface::class)->getMock();
    $this->mDba = $this->getMockBuilder(\Zend_Db_Adapter_Pdo_Abstract::class)->getMockForAbstractClass();
    $this->mService = $this->getMockBuilder(\Vendor\Module\ISomeService::class)->disableOriginalConstructor()->getMock();
    ...
    $this->obj = new Demo($this->mLogger, $this->mDba, $this->mService, ...);
} 

после чего специализируем нужное нам поведение mock'ов в соответствующей тестирующей функции:

public function test_method() {
    /* setup mocks behaviour */
    $this->mLogger->expects...
    ...
    $res = $this->obj->method();
}

Отдельное спасибо Fesor за наводку, что лучше использовать setUp(), а не костыль с extract().
+7
7k 44
Comments 41
Top of the day