Решение проблемы расхода памяти PHPUnit & Zend_Test для Zend Framework веб-приложения
На работе часть команды занимается написанием тестов для веб-приложения на Zend Framework. Надо сказать, что фреймворк довольно тяжелый, а уж в режиме тестов тем более (т.к. один процесс в этом режиме обрабатывает не один HTTP-запрос, как в обычном режиме, а целую кучу). Раньше всё было хорошо, но постепенно тестов становилось всё больше и больше, они начали интенсивно кушать память и в один прекрасный момент перестали работать совсем.Мы увеличивали лимиты (memory_limit) но потом они стали совсем уж большими (512Мб). В системе тестирования то и дело вываливались ошибки типа этой:
Allowed memory size of 33554432 bytes exhausted.
Сокращение расхода памяти в phpUnit
Поэтому стало ясно, что надо что-то менять. Погуглив данную тему, я узнал о специальном режиме работы PHPUnit – “processIsolation”. Смотрим в мануалку:
–process-isolation Run each test in a separate PHP process.
Ага, каждый тест запускается в отдельном процессе. А значит, он уж точно влезет в лимит ( ну если конечно мы не сделаем мега-тест на пол-гигабайта 🙂 ). Чтобы активировать этот режим нужно либо добавить параметр при запуске phpunit (см. выше), либо дописать его в phpunit.xml файл.
<?xml version=”1.0″ encoding=”UTF-8″ ?>
<phpunit bootstrap=”./application/bootstrap.php”
colors=”true”
convertErrorsToExceptions=”true”
convertNoticesToExceptions=”true”
convertWarningsToExceptions=”true”
processIsolation=true”
stopOnFailure=”true”
syntaxCheck=”true”>
Запустили его, и теперь расход памяти уменьшился, а тесты стали проходить.
Note1: Текущее состояние (currentState) в режиме изоляции процессов phpUnit
Тут надо отметить, что в режиме работы с изоляцией процессов могут происходить некоторые проблемы с инклюдами. Это связано с параметром $this->preserveGlobalState класса контроллера. Вот что видно в исходниках:</p> <pre class=”> $template->setVar(
array(
…
‘included_files’ => $this->preserveGlobalState ? PHPUnit_Util_GlobalState::getIncludedFilesAsString() : ”,
‘constants’ => $this->preserveGlobalState ? PHPUnit_Util_GlobalState::getConstantsAsString() : ”,
‘globals’ => $this->preserveGlobalState ? PHPUnit_Util_GlobalState::getGlobalsAsString() : ”,
…
)
);
А т.к. переменная preserveGlobalState по-умолчанию true:
1 2 3 4 5 6 |
/** * Whether or not this test should preserve the global state when running in a separate PHP process. * * @var boolean */ protected $preserveGlobalState = TRUE; |
то нам надо перопределить её.
1 2 3 4 5 6 7 8 |
class MyTestCase extends PHPUnit_Framework_TestCase { public function run(PHPUnit_Framework_TestResult $result = NULL) { $this->setPreserveGlobalState(false); return parent::run($result); } } |
Однако, у меня, вс1 было с точностью наоборот. Т.е. проблемы с инклюдами возникли после становки в false этого параметра.
Note2: Циклические ссылки и тесты
При наличии циклических ссылок, вас спасет флаг –no-globals-backup . Он отключает бэкап+рестор “глобальных” переменных при запуске теста. Опасно, но помогает при наличии в программе случайных циклических ссылок – как в PEAR, например.
Литература
http://stackoverflow.com/questions/4216027/phpunit-with-zend-framework-memory-problem
http://matthewturland.com/2010/08/19/process-isolation-in-phpunit/
http://friendfeed.com/dklab/fc835155/phpunit-php-fatal-error-allowed-memory-size-of