Подтвердить что ты не робот

Эквивалент SimpleTest "частичные mocks" в PHPUnit?

Я пытаюсь перенести кучу тестов из SimpleTest в PHPUnit, и мне было интересно, есть ли эквивалент для SimpleTest частичных mocks.

Обновление: я не могу найти ничего в документах, которые предполагают, что эта функция доступна, но мне пришло в голову, что я могу просто использовать подкласс. Это хорошая или плохая идея?

class StuffDoer {
    protected function doesLongRunningThing() {
        sleep(10);
        return "stuff";
    }
    public function doStuff() {
        return $this->doesLongRunningThing();
    }
}
class StuffDoerTest {
    protected function doesLongRunningThing() {
        return "test stuff";
    }
}
class StuffDoerTestCase extends PHPUnit_Framework_TestCase {
    public function testStuffDoer() {
        $sd = new StuffDoerTest();
        $result = $sd->doStuff();
        $this->assertEquals($result, "test stuff");
    }
}
4b9b3361

Ответ 1

Из чтения связанной страницы частичный макет SimpleTest кажется макетом, где только некоторые из методов переопределены. Если это правильно, эта функциональность обрабатывается обычным PHPUnit mock.

Внутри PHPUnit_Framework_TestCase вы создаете макет с

$mock = $this->getMock('Class_To_Mock');

Создает экземпляр mock, где все методы ничего не делают и возвращают null. Если вы хотите только переопределить некоторые из методов, второй параметр getMock - это массив методов для переопределения.

$mock = $this->getMock('Class_To_Mock', array('insert', 'update'));

создаст mock-экземпляр Class_To_Mock с удаленными функциями insert и update, готовыми для их возвращаемых значений.

Эта информация находится в phpunit docs.

Примечание, этот ответ показывает более современные примеры кода, для версий PHPUnit начиная с 5.4

Ответ 2

PHPUnit_Framework_TestCase::getMock устарел с phpunit 5.4. Вместо этого мы можем использовать setMethods.

setMethods (методы массива $) можно вызвать в объекте Mock Builder, чтобы указать методы, которые должны быть заменены с помощью настраиваемого тестового двойника. Поведение других методов не изменяется. Если вы вызываете setMethods (null), то никакие методы не будут заменены.

https://phpunit.de/manual/current/en/test-doubles.html

$observer = $this->getMockBuilder(Observer::class)
                 ->setMethods(['update'])
                 ->getMock();

Обратите внимание, что приведенный выше getMock равен PHPUnit_Framework_MockObject_MockBuilder::getMock. (Phpunit5.6)

Ответ 3

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

Однако я стараюсь избегать этого, по нескольким причинам.

Во-первых, он очень сильно сочетает ваш тест с внутренней реализацией класса. Вас действительно волнует, был ли вызван метод под названием doesLongRunningThing, или это более важно, чтобы "LongRunningThing" было сделано?

Во-вторых, когда я сталкиваюсь с этим, это всегда заставляет задуматься, есть ли у меня один класс, выполняющий работу двух. Рефакторинг класса экстрактов может быть в порядке. Тестирование становится намного проще, если doesLongRunningThing() становится его собственным классом даже с помощью одного метода.

Я считаю, что решение заключается в том, чтобы внедрять службы, на которые зависит SUT (http://en.wikipedia.org/wiki/Dependency_injection). Это также делает реализацию doesLongRunningThing более надежной.

Без перехода в интерфейсы, вот что я сделал бы:

class DoesLongRunningThing {
    public function execute() {
        sleep(10);
        return "stuff";
    }
}

class StuffDoer {
    protected $doesLongRunningThing;

    public function setLongRunningThinger(DoesLongRunningThing $obj) {
        $this->doesLongRunningThing = $obj;
    }

    public function doStuff() {
        return $this->doesLongRunningThing->execute();
    }
}

Теперь легко насмехаться:

class StuffDoerTestCase extends PHPUnit_Framework_TestCase {
    public function testStuffDoer() {
        $dlrtMock = $this->getMock('DoesLongRunningThing');
        $dlrtMock->expects($this->any())->will($this->returnValue("test stuff"));

        $sd = new StuffDoer();
        $sd->setLongRunningThinger($dlrtMock);
        $result = $sd->doStuff();
        $this->assertEquals($result, "test stuff");
    }
}