Много текста про практику работы с PHPUnit/DbUnit
PHP*
Доброго времени суток, друзья!
Хочу поделиться опытом по борьбе с PHPUnit/DbUnit в связке с MySQL. Далее небольшая предыстория.
Краткая предыстория
В процессе написания одного веб-приложения возникла необходимость тестировать код на PHP, интенсивно взаимодействующий с БД MySQL. В проекте в качестве фреймворка модульного тестирования использовался порт xUnit — PHPUnit. В результате было принято решение писать тесты для модулей, непосредственно взаимодействующих с базой, подцепив плагин PHPUnit/DbUnit. Дальше я расскажу о тех трудностях, которые возникли при написании тестов и о том, каким способом я их преодолел. В ответ же хотелось бы получить комментарии знающих людей относительно корректности моих решений.
Как работает DbUnit
Подпункт предназначен для тех, кто не знаком с методикой тестирования с использованием PHPUnit и/или DbUnit. Кому не интересно, смело можно переходить к следующему.
Далее по тексту:
тестовый класс — класс, содержащий код модульных тестов, наследник любой из реализаций PHPUnit::TestCase;
тестируемый класс — класс, который необходимо протестировать.
Так как подпункт для начинающих, то для начала будет рассмотрена процедура модульного тестирования обычных классов PHP, а потом описаны отличия при тестировании кода, взаимодействующего с БД.
Тестирование обычных классов PHP
Чтобы протестировать класс, написанный на PHP, с использованием фреймворка PHPUnit, необходимо создать тестовый класс, расширяющий базовый класс PHPUnit_Framework_TestCase. Затем создать в этом классе публичные методы, начинающиеся со слова test (если создать метод, который будет называться по-другому, он не будет автоматически вызван при прогоне тестов), и поместить в них код, выполняющий действия с объектами тестируемого класса и проверяющий результат. На этом можно закончить и скормить полученный класс phpunit, который, в свою очередь, последовательно вызовет все тестовые методы и любезно предоставит отчет об их работе. Однако в большинстве случаев в каждом из тестовых методов будет повторяющийся код, подготавливающий систему для работы с тестируемым объектом. Для того, чтобы избежать дублирования кода, в классе PHPUnit_Framework_TestCase созданы защищенные методы setUp и tearDown, имеющие пустую реализацию. Эти методы вызываются перед и после запуска очередного тестового метода соответственно и служат для подготовки системы к выполнению тестовых действий и очистки ее после завершения каждого теста. В тестовом классе, расширяющем PHPUnit_Framework_TestCase, можно переопределить эти методы и поместить повторяющийся ранее в каждом тестовом методе код в них. В результате последовательность вызова методов при прогонке тестов будет следующая:
setUp() {/* Установили систему в нужное состояние */}
testMethod1() {/* протестировали метод 1 класса */}
tearDown() {/* Очистили систему */}
setUp() {/* Установили систему в нужное состояние */}
testMethod2() {/* протестировали метод 2 класса */}
tearDown() {/* Очистили систему */}
…
setUp() {/* Установили систему в нужное состояние */}
testMethodN() {/* протестировали метод N класса */}
tearDown() {/* Очистили систему */}
Тестирование кода PHP, взаимодействующего с БД
Процесс написания тестов для кода, взаимодействующего с БД, практически не отличается от процедуры тестирования обычных классов PHP. Сначала необходимо создать тестовый класс, наследующий PHPUnit_Extensions_Database_TestCase (класс PHPUnit_Extensions_Database_TestCase сам при этом наследует PHPUnit_Framework_TestCase), который будет содержать тесты для методов тестируемого класса. Затем создать тестовые методы, начинающиеся с префикса test, а потом скормить этот код phpunit с указанием имени тестового класса. Отличия заключаются лишь в том, что в тестовом классе обязательно необходимо реализовать два публичных метода — getConnection() и getDataSet(). Первый метод необходим для того, чтобы научить DbUnit работать с БД (придется использовать PDO), а второй для того, чтобы сообщить фреймворку, в какое состояние переводить базу данных перед выполнением очередного теста. Под DataSet в терминологии DbUnit понимается набор из одной или более таблиц.
Как говорилось выше, перед выполнением очередного теста (представленного методом в тестовом классе), PHPUnit вызывает специальный метод setUp(), чтобы сэмулировать среду выполнения для объекта тестируемого класса. В случае DbUnit реализация по умолчанию метода setUp() уже не пустая. Если говорить в общих чертах, то внутри метода setUp() будет создан некий объект databaseTester, который, используя определенный нами метод getConnection(), переведет базу в состояние, представленное набором таблиц (DataSet`ом), получаемым при вызове метода getDataSet(). Если вы были внимательны, то реализация метода getDataSet() также должна предоставляться тестовым классом, т.е. нами. В результате получим похожую последовательность вызовов
setUp() {/* Установили БД в соответствии с данными, получаемыми от
метода getDataSet() */}
testMethod1() {/* протестировали метод 1 класса */}
tearDown() {/* Очистили систему */}
setUp() {/* Установили БД в соответствии с данными, получаемыми от
метода getDataSet() */}
testMethod2() {/* протестировали метод 2 класса */}
tearDown() {/* Очистили систему */}
…
setUp() {/* Установили БД в соответствии с данными, получаемыми от
метода getDataSet() */}
testMethodN() {/* протестировали метод N класса */}
tearDown() {/* Очистили систему */}
Маленькие неприятности
Оперативная обстановка: База данных, используемая в проекте, имеет несколько десятков таблиц, движок MySQL InnoDB. Механизм внешних ключей активно используется с целью поддержания согласованности данных на уровне самой БД.
1. Инициализация базы
Первая неприятность, которая начала омрачать мне процесс тестирования — инициализация базы данных созданными мной наборами таблиц.
DbUnit позволяет создавать DataSet`ы, получая данные из различных источников:
Flat Xml — такой простенький способ описание состояния БД в xml-файле, рассчитанный преимущественно на ручное формирование файла.
Xml — полноценный формат задания состояния, намного больше букаф, но и более широкие возможности (можно задавать null-значения, более точно описывать структуру БД и пр.).
MySQL Xml — разновидность предыдущего формата, любезно предоставленная разработчиками DbUnit, позволяющая создавать объект DataSet на основании экспорта данных БД утилитой mysqldump.
Создание объекта DataSet по текущему состоянию БД.
Каждый из вышеперечисленных способов создания наборов таблиц реализуется отдельным методом класса PHPUnit_Extensions_Database_TestCase.
Я избрал себе в помощники mysqldump и ринулся в атаку: сформировал нужное состояние базы, выгрузил его в xml и в реализации getDataSet() написал что-то вроде:
Поделитесь с Вашими друзьями: |