Тестирование объектов с зависимостями в PHPUnit - PullRequest
9 голосов
/ 13 февраля 2009

Для объектов, которые составляют другой объект как часть их реализации, каков наилучший способ написания модульного теста, чтобы тестировался только основной объект? Тривиальный пример:

class myObj { 
    public function doSomethingWhichIsLogged()
    {
        // ...
        $logger = new logger('/tmp/log.txt');
        $logger->info('some message');
        // ...
    }
}

Я знаю, что объект мог бы быть спроектирован так, чтобы зависимость объекта регистратора могла быть введена и, следовательно, смоделирована в модульном тесте, но это не всегда так - в более сложных сценариях вам нужно создавать другие объекты или делать вызовы статическим методам.

Поскольку мы не хотим проверять объект регистратора, только myObj, как мы продолжим? Создаем ли мы «двойник» с помощью тестового сценария? Что-то вроде:

class logger
{
    public function __construct($filepath) {}
    public function info($message) {}
}

class TestMyObj extends PHPUnit_Framework_TestCase 
{
    // ...
}

Это кажется выполнимым для небольших объектов, но будет затруднительно для более сложных API, где SUT зависит от возвращаемых значений. Кроме того, что, если вы хотите протестировать вызовы объекта зависимости так же, как вы можете использовать с фиктивными объектами? Есть ли способ высмеивать объекты, которые создаются SUT, а не передаются?

Я прочитал справочную страницу по макетам, но, похоже, она не охватывает ситуацию, когда зависимость составлена, а не агрегирована. Как ты это делаешь?

Ответы [ 4 ]

6 голосов
/ 13 февраля 2009

После troelskn посоветуйте вот основной пример того, что вы должны сделать.

<?php

class MyObj
{
    /**
     * @var LoggerInterface
     */
    protected $_logger;

    public function doSomethingWhichIsLogged()
    {
        // ...
        $this->getLogger()->info('some message');
        // ...
    }

    public function setLogger(LoggerInterface $logger)
    {
        $this->_logger = $logger;
    }

    public function getLogger()
    {
        return $this->_logger;
    }
}


class MyObjText extends PHPUnit_Framework_TestCase
{
    /**
     * @var MyObj
     */
    protected $_myObj;

    public function setUp()
    {
        $this->_myObj = new MyObj;
    }

    public function testDoSomethingWhichIsLogged()
    {
        $mockedMethods = array('info');
        $mock = $this->getMock('LoggerInterface', $mockedMethods);
        $mock->expects($this->any())
             ->method('info')
             ->will($this->returnValue(null));

        $this->_myObj->setLogger($mock);

        // do your testing
    }
}

Более подробную информацию о ложных объектах можно найти в руководстве .

4 голосов
/ 13 февраля 2009

Как вы, кажется, уже знаете, Зависимости конкретного класса делают тестирование трудным (или просто невозможным). Вы должны отделить эту зависимость. Простое изменение, которое не нарушает существующий API, заключается в том, чтобы по умолчанию использовать текущее поведение, но предоставить хук для его переопределения. Это можно реализовать несколькими способами.

В некоторых языках есть инструменты, которые могут вводить фиктивные классы в код, но я не знаю ничего подобного для PHP. В большинстве случаев вам, вероятно, будет лучше в любом случае реорганизовать свой код.

0 голосов
/ 05 марта 2010

На самом деле есть довольно новое расширение для перегрузки классов PHP, выпущенное теми же парнями, которые собирают PHPUnit. Он позволяет вам переопределить новый оператор в тех случаях, когда вы не можете изменить код, к сожалению, его не так просто установить в Windows.

URL-адрес http://github.com/johannes/php-test-helpers/blob/master/

0 голосов
/ 13 февраля 2009

Похоже, я неправильно понял вопрос, позвольте мне повторить попытку:

Вы должны использовать шаблон синглтона или фабрику для регистратора, если еще не слишком поздно:

class LoggerStub extends Logger {
    public function info() {}
}
Logger::setInstance(new LoggerStub());
...
$logger = Logger::getInstance();

Если вы не можете изменить код, вы можете использовать универсальный класс, который перегружает __ call ()

class GenericStub {
    public function __call($functionName, $arguments) {}
}
...