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

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

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

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

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

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

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

Вот интересная статья, в которой говорится о насмешливых глобальных 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()); } } 

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

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

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

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