Издевательские функции 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()
. Вот хороший пример с картой издевательств