Написание автоматических тестов и среда phpUnit
Программы, которые не тестировали, НЕ РАБОТАЮТ.
Б. Страуструп ВведениеГлавная задача профессионального php-разработчика - cоздание в максимально короткий срок программного обеспечения, полностью удовлетворяющего заказчика. Скрипты должны делать именно то, чего от них ожидают. Самый простой пример - форма обратной связи, которая по нажатию на кнопку при условии корректного заполнения полей, отправляет почту или записывает данные в таблицу БД. Если она этого не сделала - совершенно очевидно, что в программу закралась ошибка. Итак, тестирование - деятельность, направленная на выявление такого рода несоответствий между ожидаемым и действительным. Выявляя несоответствия, некорректное поведение на стадии разработки, разработчик сознательно и планомерно уменьшает вероятность того, что с этим придется столкнуться пользователю. Тестировать программы (в частности php-скрипты) вполне можно и вручную. На примере формы обратной связи, понятно, что протестировать ее очень просто. Запустить браузер, зайти на нужный url, заполнить поля, нажать на "Ок", потом зайти в базу и проверить, появилась ли там соотвeтствующая запись. Прекрасно, но после любого изменения скрипта нельзя быть уверенным в его работоспособности. Можно понадеяться на авось и не проверять, работает ли скрипт после очередной модификации или нет, но помните, что если его не протестируете вы, это сделает заказчик во время сдачи проекта или конечный пользователь во время эксплуатации. В первом случае это грозит вам финансовой нестабильностью, а во втором пострадает ваш имидж. Для того, чтобы не тестировать вручную (это долгий, сложный и требующий особой усидчивости процесс), можно написать программу, тестирующую другие программы, или создать автоматизированные тесты. В любой современной методологии разработки ПО тестирование является неотъемлемой частью процесса, а значимость его написания не ниже значимости написания кода. Профессиональный разработчик обязан создавать наборы различного вида автоматизированных тестов (подробнее о видах тестов, правилах их именования и т.д. можно прочитать в статье Кирилла Максимова "Организация и именование автоматизированных тестов"). Несмотря на очевидную полезность и акцент методологий на процессе тестирования, личный опыт авторов свидетельствует о следующем разбросе мнений web-программистов насчет автоматизированного тестирования собственных скриптов:
Автоматизированные тесты и web-программированиеБудни основной массы web-разработчиков - выбор хорошего сервера для размещения сайта. Трудно найти хостинг-провайдера (php+mysql за разумные деньги), который устроил бы сразу на 100%, а пeреезд на новый сервер даже для интерпретируемого языка, коим является мой горячо любимый PHP, может стать "фатальным". Даже в документации по Java - языке, изначально ориентированном на кроссплатформенность, написано, что цена переносимости никогда не равна нулю. Эти люди знают, что говорят. После переезда в прекрасно отлаженных и стабильно работающих ранее скриптах могут появиться warning'и, а какие-то программы перестанут работать совсем: поменялись пути, библиотеки, версия самого php. В итоге, имея набор тестов, переехать гораздо проще. Следует переписать скрипты, запустить набор тестов, посмотреть те тесты, которые завершились неудачно, разобраться, поправить, запустить набор тестов еще раз. Все. Вместо того, чтобы оставить "полуживой" проект на новом хостинге, "поверхностно" прокликав в браузере по ссылкам, мы получаем стабильно работающий сайт на новом сервере без особой головной боли. Автоматизированное тестирование в конечном итоге повышает качество кода, и учитывая специфику web-программирования, упрощает процесс переезда с сервера на сервер. Вслед за решением тестировать, приходит необходимость как-то организовать этот процесс. Например, выделить для тестовых скриптов отдельную директорию, создать программу, которая запустит все скрипты и в зависимости от их исхода напишет "OK" или "Error". Вполне возможно проделать это "с нуля", но есть готовые тестовые инфраструктуры (вспомогательные программы), которые объединяют группы тестов "по темам", единообразно их сохраняют, запускают и выводят результаты. Cоздание тестов в phpUnitВ основном из-за увлеченности методологией XP авторы знакомы с тестовой инфраструктурой JUnit, соответственно, для php их выбор пал на phpUnit, как на созданный энтузиастами порт своего "старшего брата". На сегодняшний день существует несколько различных версий порта JUnitї на php. Как минимум 3 варианта c названием phpUnit и порт php_simpletest. Регулярно обновляется версия PEAR::PHPUnitї (Sebastian Bergmann). Php_simpletestї находится в состоянии первой беты, другие phpUnit'ы обновлялись год назад (phpUnit projectї) и два года назад (phpunit-1.0.0ї) соответственно. Все рассмотренные ниже примеры модульных тестов написаны с использованием PEAR::PHPUnit. Создадим набор тестов для класса Message, который форматирует и проверяет сообщение для последующей отправки его по e-mail. Конструктор класса получает 3 параметра (имя отправителя, его e-mail aдрес и тело сообщения). Метод format_message() форматирует сообщение перед отправкой, а is_valid() проверяет возможность отправки сообщения (заполнены ли поля, и корректно ли введен е-mail). Исходный код класса Message - здесьї "Сердцем" тестирования является класс PHPUnit, который запускает TestSuite (набор тестов) и возвращает объект TestResult (результат теста). Для того, чтобы написать минимальный набор тестов, используя phpUnit, необходимо:
Создадим набор тестов для класса, следуя приведенной инструкции. Мы протестируем следующие аспекты работы класса Message: его реакцию на пустое сообщение, и некорректный e-mail, а также сравним отформатированное объектом класса сообщение с эталонным. Создаем сообщение, подаем на вход конструктора неверный e-mail, ожидаем, что метод класса is_valid(), ответсвенный за его проверку, возвратит 'false'. function test_email_invalid() { $m = new Message('name', 'invalid', 'body'); $this->assertFalse($m->is_valid()); /* ожидаем, что m->is_valid() возвращает false */ } Создаем сообщение, подаем на вход конструктора пустые строки, ожидаем, что метод класса is_valid(), так же как и в прошлый раз вернет false. function test_empty_input() { $m = new Message('', '', ''); $this->assertFalse($m->is_valid()); /* ожидаем, что m->is_valid() возвращает false */ } И, наконец, подаем на вход корректно сформированное сообщение и ожидаем, что класс его правильно сформатирует function test_valid_input() { $m = new Message('name', 'invalid@mail.ru', 'body'); $this->assertTrue($m->is_valid()); $valid_string = <<<EOL from: name (invalid@mail.ru) body EOL; $this->assertEquals($valid_string, $m->as_string()); /* ожидаем, что эталонная строка и результат работы as_string() совпадут */ } }
Запускаем набор тестов (полная версия testmessage.php - здесьї) и получаем следующий результат:
Все тесты завершились успешно. Теперь можно продолжить наращивание функциональности класса Message, будучи уверенным, что после очередной модификации он корректно работает (конечно при условии, что сработали все тесты). Разработка класса Message и разработка Message c набором тестов не очень сильно различаются по времени, но во втором случае мы имеем мощную "поддержку" на будущее, над каждой функцией мы размышляем как минимум дважды, и позже можем легко определить работоспособность скрипта на новой программно-аппаратной конфигурации.
Гусев Антон (tony_@mail.ru) Материалы:
|