Размер
Тестирование базы данных на PHP (PHPUnit, DbUnit)
Play

Тестирование базы данных на PHP (PHPUnit, DbUnit)

Занимаюсь рефакторингом сервиса и решил написать для своих классов тесты. Класс самый простой, для коннекта к базе использует PDO, никаких фреймворков не задействовано. Выбор пал на PHPUnit, что, наверное, не удивительно.

Но вот не всё понятно именно по методологии такого тестирования. Судя по тому, что я прочитал и успел накодить, нужно:

1) Завести отдельную тестовую базу 2) Набросать фикстур (я использую yaml формат) 3) Выполнить тесты

Ну а как же быть дальше? Вот допустим поменяется структура какой-либо таблицы, к примеру возьмёт барабашка и дропнет колонку `id` в таблице `users`. Запустит тесты — и всё ж замечательно, фикстуры-то прежние. То есть это геймор в виде постоянного ручного контроля? — имхо не есть гут.

И ещё увидел в презентации совет по возможности избегать тестирования бд с ипользованием серверов (акцент был сделан на скорости выполнения), а использовать mocks&stubs. Вот это хотелось бы узнать поподробнее — на основе чего их генерировать, как отслеживать изменения и прочая? Может кто сталкивался с подобной задачей, сейчас ведь все тестируют, я это чувствую :)

Заранее благодарен за любую помощь.

  • Вопрос задан более трёх лет назад
  • 7555 просмотров
  • Facebook
  • Вконтакте
  • Twitter

Мне в своё время очень помогла разобраться с тестированием книжка Perl Testing: A Developer's Notebook. В подходе к тестированию особой разницы между Perl и PHP нет, так что Вам она тоже может оказаться полезна. Перевода, к сожалению, я по-быстрому не нашёл, возможно она есть только на английском.

При тестировании один из ключевых моментов — нужно постоянно очень хорошо отдавать себе отчёт в том, что именно мы сейчас тестируем. А тестируем мы обычно наш код, а не окружающую среду (вроде базы данных) — в тестах мы предполагаем, что база данных работает корректно, вопрос в том, корректно ли наш код работает с базой данных. Поэтому, да, можно тестировать работу с базой даже без самой базы — mock-нув функции отправляющие SQL-запросы в базу, и в тесте проверяя, что наш код генерирует именно те SQL-запросы, в том порядке и с теми значениями, которые он должен был сгенерировать (а заодно и подставляя тестируемому коду нужные нам данные под видом «ответа от базы на SQL-запрос»). И да, на реальной базе данных этот код может не работать корректно, не смотря на то, что тесты он проходит — просто потому, что он генерирует не те SQL-запросы, которые нужно генерировать для этого сервера БД. Но это не имеет отношения к тестам — они свою работу выполнили: подтвердили, что код работает именно так, как ожидается. А если задача была поставлена неверно, и код должен делать что-то другое — это проблема постановки задачи, а не тестирования.

Барабашка портящий базу — это не проблема тестирования кода. Хотите написать тест против барабашек — сравнивайте в тесте схему базы с эталонной, и правьте тест после любого изменения схемы базы. Но смысла в этом нет.

Совместимость фикстур из тестов с текущей схемой базы проверить очень просто: при запуске теста создавайте тестовую базу данных используя текущую схему основной базы данных, а в фикстурах храните только данные, без схемы таблиц.

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

  • Facebook
  • Вконтакте
  • Twitter

Спасибо за подробный ответ.

По поводу автоматической генерации и сравнения схем — об этом я тоже подумал, и думаю смысл всё же в этом есть. Ещё можно фикстуры генерить автоматически, основываясь на данных рабочей базы, потому что протестировать нужно очень много случаев, и необходимо чтобы различные связи между данными таблиц были соблюдены. Например есть пользователи и их посты. Нужно обязательно чтобы в фикстурах были юзеры, у которых есть один пост, несколько, ни одного. Конечно, при первичном формировании таких данных будет использоваться текущий, то бишь не протестированный код, но в принципе должно получиться.

Но всё равно главное осталось мне неясным :(( Задача стоит протестировать не код, а работу сервиса, и то, что сервис обращается к БД, как раз дело и усложняет («предпологаем что бд работает корректно» — она-то так и работает, mysql отлаживают другие люди, а вот какие туда вызовы поступают — уже моя ответственность). Какие есть способы протестировать работу с базой без задействования базы? Какие именно данные нужно mock'нуть и где что проверять?

вот есть например такой класс

Как именно и что нужно мокапить в функции, тестирующей добавление пользователя? Значит ли это, что нужно править тестируемый метод, чтобы он не в бд ещё писал, а делал нечто другое? Ведь, так или иначе, в коде теста вызывается тестируемый метод, пишущий в базу. Как узнать, что он корректно отработал, не обращаясь к БД?

  • Facebook
  • Вконтакте
  • Twitter

Проверить что метод выдал корректный SQL с помощью моков и/или стабов у вас не получится. Если стоит задача проверки, что User::create() генерирует «INSERT INTO `users` . » (а вернее, вызывает соответствующие методы PDO — ему же мы доверяем, что он корректно сгенерирует SQL?), то мокапьте $this->pdo, проверяя параметры, передаваемые в prepare() и execute(), чтобы они получали «INSERT INTO `users` . » и $params соответственно.

Если же стоит задача проверять, что User::create() корректно пишет в БД, то это не юнит-тест уже получается, а интеграционный, проверяющий, что несколько подсистем приложения (ваш код, код pdo и код MySQL) вместе работают нормально и моки/стабы здесь не уместны.

P.S. А вообще работу с БД я бы вынес на отдельный уровень, в том числе и для облегчения тестирования. Конечно, если у вас не простое CRUD приложения и в классе User пристуствует не только работа с БД, а в других классах нет таких же функций create() с только $this->pdo->prepare() и $this->pdo->execute(), различающимися лишь именами таблиц и полей в INSERT.

В дополнение к powerman Я могу выделить такие ситуации: 1) тест функционала или классов, не имеющих сильной зависимости от внешних сервисов 2) тоже самое, но со слишком обширным использованием DAO 3) интеграционные тексты

В первом случае можно спокойно мокировать запросы в БД или иные внешние сервисы. Во втором случае по возможности использую обертывание в транзакцию и откат в самом конце теста (методы setUp и tearDown). Если нельзя, то тоже самое что в п.3 В третьем случае делается всё «на живую» в девелоперской среде или использовать тестовую базу и естественно иметь скрипт генерирующий окружение.

📎📎📎📎📎📎📎📎📎📎