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

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

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

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");
    }
}

5 ответов

Решение

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

Внутри PHPUnit_Framework_TestCaseсоздаешь макет с

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

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

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

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

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

Обратите внимание: в этом ответе приведены более свежие примеры кода для версий PHPUnit, начиная с версии 5.4.

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

setMethods (array $ методов) можно вызвать в объекте 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)

Методы setMethodsустарели. Теперь работает:

      $mock = $this->getMockBuilder(ClassToMock::class)
    ->onlyMethods(['insert', 'update'])
    ->getMock();

Я не думаю, что 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");
    }
}

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

      $mockController = $this->getMockBuilder('ControllerClassName')
            ->setMethods(['functionToMock'])
            ->getMock();

$result = $mockController->anotherFunction();
$this->assertEquals(true, $result);
Другие вопросы по тегам