Издевательские функции PHP в модульных тестах

Я тестирую код PHP с помощью SimpleTest, и я столкнулся с проблемой. В моих тестах класса базы данных я хочу уметь задавать функции PHP mysql. В моих тестах класса-оболочки для функции mail я хочу, чтобы mock PHPs mail функция. Это лишь некоторые примеры.

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

У меня есть опыт тестирования кода Ruby, а Test:: Unit и RSpec очень легко тестируют код изолированно. Я новичок в тестировании PHP, и мне кажется, что я тестирую намного больше, чем мне нужно, чтобы пройти мои тесты.

Есть ли способ в SimpleTest или PhpUnit или какой-либо другой структуре тестирования, что делает это возможным или проще?

Ответ 1

Не в автоматическом режиме. Что вы можете сделать, это написать свой код таким образом, чтобы внешние зависимости были обернуты в объекты, которые передаются извне. В вашей производственной среде вы просто подключите настоящие адаптеры, но во время тестирования вы можете подключить его к заглушкам или издевательствам.

Если вы действительно настаиваете, вы можете использовать расширение runkit extension, которое изменяет модель программирования php, чтобы вы могли переопределять классы и функции во время выполнения. Тем не менее, это внешние и нестандартные расширения, поэтому это важно. Стандарт defacto - это ручной подход, как я описал выше.

Ответ 2

Вот интересная статья, в которой говорится о насмешливых глобальных php-функциях. Автор предлагает очень творческое решение "Mock" (отследить) глобальные функции php, перезаписав методы внутри пространства имен SUT (тестируемый сервис).

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

<?php

namespace My\Namespace;

use PHPUnit_Framework_TestCase;

/**
 * Override time() in current namespace for testing
 *
 * @return int
 */
function time()
{
    return SomeClassTest::$now ?: \time();
}

class SomeClassTest extends PHPUnit_Framework_TestCase
{
    /**
     * @var int $now Timestamp that will be returned by time()
     */
    public static $now;

    /**
     * @var SomeClass $someClass Test subject
     */
    private $someClass;

    /**
     * Create test subject before test
     */
    protected function setUp()
    {
        parent::setUp();
        $this->someClass = new SomeClass;
    }

    /**
     * Reset custom time after test
     */
    protected function tearDown()
    {
        self::$now = null;
    }

    /*
     * Test cases
     */
    public function testOneHourAgoFromNoon()
    {
        self::$now = strtotime('12:00');
        $this->assertEquals('11:00', $this->someClass->oneHourAgo());
    }
    public function testOneHourAgoFromMidnight()
    {
        self::$now = strtotime('0:00');
        $this->assertEquals('23:00', $this->someClass->oneHourAgo());
    }
}

Не уверен, что это хороший способ сделать это, но он, безусловно, хорошо работает, и я думаю, что здесь стоит упомянуть. Может быть, пища для обсуждения...

Ответ 3

Существует расширение PHPUnit, которое использует runkit внутренне и способно использовать вызовы, ограничения параметров и все, что может сделать издевательский объект.

https://github.com/tcz/phpunit-mockfunction

Ответ 4

В среде PHP 5.3+ вы можете обходиться с необходимостью использовать расширение runkit путем взлома пространств имен. Единственное требование в том, что вызовы функций не используют полностью квалифицированное пространство имен, например \mysql_query() - и обычно этого не делают. Затем вы можете определить ту же функцию в своем тесте, указав тест в пространстве имен, а PHP будет вызывать вашу функцию вместо глобальной. Лично я использую этот подход, чтобы заглушить вызов функции time(). Вот хороший пример с картой издевательств