» Главная
eXcode.ru » Статьи » PHP » Трюки
» Новости
» Опросы
» Файлы
» Журнал
» Форум
Пользователей: 0
Гостей: 6





Tdd: Шаг первый - функциональные тесты




Шаг первый - функциональные тесты

План действий

Изменения носят глобальный характер и без практически полного переписывания кода нам не обойтись. Двигаться следует небольшими, но решительными шагами. План таков:

  • используем SimpleTest (версия 1.0, http://simpletest.sourceforge.net) в качестве среды для функционального и модульного тестирования.
  • используем WACT (версия 0.2alpha, http://wact.sourceforge.net) в качестве шаблонной системы и в качестве DBAL(Database Abstraction Layer)
  • используем PHPMailer (версия 1.73, http://phpmailer.sourceforge.net) для формирования уведомлений по электронной почте

Все внешние библиотеки будем хранить в директории /external нашего приложения.

Функциональное тестирование

Наша задача - обезопасить функциональность приложения от будущих изменений, то есть мы должны гарантировать, что все наши будущие рефакторинги не приведут к тому, что мы потеряем часть его фукциональности приложения. Для этого существуют функциональные тесты. Для создания функциональных тестов мы будем использовать входящую в SimpleTest подсистему WebTester. Подробную информацию можно получить:

WEB тестирование при помощи SimpleTest

Работа с библиотекой SimpleTest при web тестировании определенным образом напоминает работу непосредственно с браузером. WebTester по сути предоставляет удобные методы, эмулирующие браузер, а именно:

  • получение страниц по адресу
  • навигирование по ссылкам и кнопкам
  • заполнение и отправление форм
  • организация непосредственных GET, POST, HEAD запросов
  • эмуляция фреймов
  • формирование HTTP заголовков
  • установка и модификация cookies

Кроме этого WebTester позволяет посмотреть на запрос «изнутри»:

  • вывести на экран дамп данных запроса
  • отобразить HTTP заголовки
  • показать исходный код полученной страницы

И конечно же, самой главной особенностью WebTester является способность проверить полученные в процессе браузинга результаты, вот лишь некоторые возможности:

  • сравнить контент страницы на предмет совпадения с некоторым регулярным выражением
  • проверить содержимое <title> тега
  • проверить наличие ссылок и их содержимого
  • удостовериться в правильности содержимого полей формы
  • проверить cookie на содержимое
  • проверить HTTP заголовки

Для всего вышеописанного WebTester предоставляет исключительно чистые и понятные интерфейсы, превращающие работу с ним в удовольствие, и скоро в этом убедитесь.

Вводим функциональные WEB тесты для нашего приложения

Подготавливаем тестовую среду

Для начала создадим директорию /tests, в которой будут располагаться все тесты для нашего приложения. В этой директории создадим файл runtests.php следующего содержания:

<?php
require_once(dirname(__FILE__) . '/setup.php');
 
class AllTests extends GroupTest {
    function AllTests() {
        $this->GroupTest('All tests for feedback project');
        $this->addTestFile('acceptance_tests.php');
    }
}
 
$test =& new AllTests();
if (SimpleReporter::inCli()) {
    exit ($test->run(new TextReporter()) ? 0 : 1);
}
 
$test->run(new HtmlReporter());
?>

Этот скрипт будет точкой входа для всех тестов, причем его можно запускать как из консоли, так и из браузера. Для работы этого скрипта нам также потребуются файлы setup.php и acceptance_tests.php. В setup.php мы будем хранить глобальные настройки для всех тестов. Пока мы в нем только подключаем библиотеку SimpleTest и определяем адрес web хоста с приложением:

<?php
 
define('SIMPLE_TEST', dirname(__FILE__) . '/../external/simpletest/');
define('FEEDBACK_PROJECT_HOST', 'http://localhost/feedback/');
 
if (!file_exists(SIMPLE_TEST . '/browser.php')) {
  die ('Make sure the SIMPLE_TEST constant is set correctly in this file(' . SIMPLE_TEST . ')');
}
 
require_once(SIMPLE_TEST . '/web_tester.php');
require_once(SIMPLE_TEST . '/reporter.php');
require_once(SIMPLE_TEST . '/unit_tester.php');
require_once(SIMPLE_TEST . '/mock_objects.php'); 
?>

Первые тесты

В acceptance_tests.php будут располагаться все функциональные тесты для приложения. Не долго раздумывая, поместим в него самый первый тест:

<?php
class AcceptanceTestOfFeedbackProject extends WebTestCase {
    function testOfIndexPage() {
        $this->get(FEEDBACK_PROJECT_HOST);
        $this->assertWantedPattern('/Обратная связь/');
    } 
 ?>

Суть данного теста сводится к посещения главной страницы нашего приложения и удостоверению, что страница содержит текст «Обратная связь». Допустим, что этот тест сработал, теперь можно перейти к более сложному тесту, целью которого будет проверка правильности отправки формы. Однако мы помним, что приложение сейчас работает с продукционной базой данных, что крайне опасно!!! Нам необходимо некоторым образом заставить приложение работать с другими настройками БД - тестовыми. К счастью, оригинальные разработчики решили хранить конфигурационные данные в отдельном файле db.php, который подключается в index.php. Мы можем на время тестов заменять db.php другим файлом, в котором находятся тестовые настройки. Но как это сделать лучше всего?

Устанавливаем фикстуру

Каждый тестовый прецедент должен в идеале быть независимым, атомарным и выполняться в «чистой» среде, в которой не осталось мусора от выполнения предыдущих прецедентов.

SimpleTest позволяет подготовить некоторую окружающую среду для каждого тестового прецедента. Такая окружающая среда называется фикстурой(fixture). Сделать это можно при помощи методов setUp() и tearDown(). Эти методы вызываются соответсвенно до и после каждого тестового метода, что дает возможность разработчику произвести определенные подготавливающие мероприятия(очистка/заполнение БД, удаление временных файлов и проч).

Как упоминалось ранее, сделам так, чтобы на время тестов настройки базы данных подменивались тестовыми значениями. Для этого в директории tests создадим файл db.php - аналог того, который находится в корне приложения.

<?php
$db_host =      'localhost';
$db_name =      'feedback-web-tests';
$db_user =      'root';
$db_password =  'test';
?>

Теперь напишем фикстуру, подменяющую эти файлы перед каждым тестовым прецедентом. Также заставим фикстуру полностью очищать таблицу feedback, чтобы каждый тестовый прецедент имел «чистую» окружающую среду.

<?php
class AcceptanceTestOfFeedbackProject extends WebTestCase {
[...]
    function setUp() {
        $this->_switchToWebTestingDb();
    }
 
    function tearDown() {
        $this->_switchToProductionDb();
    }
 
    function _switchToWebTestingDb() {
        $project_dir = dirname(__FILE__) . '/../';
        $tests_dir = dirname(__FILE__) . '/';
 
        include($tests_dir . 'db.php');
        $conn = mysql_connect($db_host, $db_user, $db_password);
        mysql_select_db($db_name, $conn);
        mysql_query('DELETE FROM feedback', $conn);
 
        rename($project_dir . 'db.php', $project_dir . 'db.php~');
        copy($tests_dir . 'db.php', $project_dir . 'db.php');
    }
 
    function _switchToProductionDb() {
        $project_dir = dirname(__FILE__) . '/../';
        unlink($project_dir . 'db.php');
        rename($project_dir . 'db.php~', $project_dir . 'db.php');
    }
}
?>

Теперь когда наша продукционная база данных защищена от фатальных последствий можно приступать к тестирования отправки новых сообщений с формы.

Тестируем отправку данных с формы

Итак, тестовый случай будет выглядеть так:

<?php
class AcceptanceTestOfFeedbackProject extends WebTestCase {
[...]
    function testOfSimpleSubmitFeedback() {
        $this->_addFeedback($name = 'Bobby',
                            $email = 'email@dot.com',
                            $message = "This a message with `non-escaped characters`");
 
        $this->assertWantedPattern('/' . preg_quote($email) . '.*' .
                                         $name . '.*' .
                                         $message . '/s');
    }
 
    function _addFeedback($name, $email, $message) {
        $this->get(FEEDBACK_PROJECT_HOST);
        $this->setField('name', $name);
        $this->setField('email', $email);
        $this->setField('message', $message);
        $this->clickSubmitByName('submit');
        sleep(1);
    }
}
?>

Как вы успели заметить, мы также добавили внутренний метод, _addFeedback, который заполняет поля формы и отсылает ее. Этот метод окажется весьма кстати в последующих тестах. Постоянный рефакторинг тестов - не менее важная задача, чем рефакторинг тестируемого кода. Чтобы избежать ситуации когда у нас может быть несколько сообщений, пришедших в одно и то же время, мы принуждаем PHP «засыпать» на 1 секунду после добавления каждого сообщения.

Этот тест также успешно срабатывает.

Более сложные тесты

Добавим метод, проверяющий, что при выводе сообщения обрабатываюся на предмет небезопасных символов и тегов.

<?php
class AcceptanceTestOfFeedbackProject extends WebTestCase {
[...]
    function testOfEscapingUserInput() {
        $this->_addFeedback('<script>',
                            '<br>',
                            '"\'');
 
        $this->assertWantedPattern("/&lt;br&gt;.*&lt;script&gt;.*\\\&quot;\\\&#039;/s");
    }
}
?>

Теперь напишем тест, проверяющий правильность работы пейджера при добавлении нескольких сообщений:

<?php
class AcceptanceTestOfFeedbackProject extends WebTestCase {
[...]
    function testOfPager() {
        $this->get(FEEDBACK_PROJECT_HOST);
        $this->assertNoLink("<");
        $this->assertNoLink(">");
 
        for($i=1; $i<8; $i++) {
            $this->_addFeedback('Robot' . $i,
                                'robot' . $i . '@usrobotics.com',
                                'Hello i am Robot' . $i);
        }
 
        $this->get(FEEDBACK_PROJECT_HOST);
        $this->assertWantedPattern('/Robot7.*Robot6.*Robot5/s');
        $this->assertNoLink("<");
        $this->assertLink(">");
 
        $this->clickLink(">");
        $this->assertWantedPattern('/Robot4.*Robot3.*Robot2/s');
        $this->assertLink("<");
        $this->assertLink(">");
 
        $this->clickLink(">");
        $this->assertWantedPattern('/Robot1/');
        $this->assertLink("<");
        $this->assertNoLink(">");
 
        $this->clickLink("<");
        $this->assertWantedPattern('/Robot4.*Robot3.*Robot2/s');
        $this->assertLink("<");
        $this->assertLink(">");
 
        $this->clickLink("<");
        $this->assertWantedPattern('/Robot7.*Robot6.*Robot5/s');
        $this->assertNoLink("<");
        $this->assertLink(">");
    }
}
?>

Пейджер выводит по 3 сообщения, поэтому мы добавляем в тесте 8 сообщений, чтобы проверить граничные ситуации. В этом тесте мы также воспользовались методом clickLink класса WebTestCase, который позволяет проэмулировать навигирование пользователся по ссылкам.

Эти тесты покрывают весь функционал приложения, поэтому, убедившись в том, что все работает, мы приступаем к долгожданному рефакторингу приложения.

Далее - Шаг второй - отделяем бизнес логику от презентационной логики .

К началу статьи





Добавил: Дата публикации: 2008-03-04 11:05:22

Рейтинг статьи:0.00 [Голосов 0]Кол-во просмотров: 4112
Оцените статью:

Комментарии читателей

Всего комментариев: 1

2009-09-26 14:43:13
WharbabyThala
[url=http://spbhotnews.ru]Питерские горячие новост[/url]и

Разительно интересно. Но чего-то не хватает. Может надевать, стоит добавить каких-нибудь картинок или фото?
Ваше имя: *
Текст записи: *
Имя:

Пароль:



Регистрация

Как вы относитесь к рекламе на сайтах.
Отрицательно, терпеть ее не могу!
46% (95)
С пониманием
24% (50)
Пусть будет, если только по делу
15% (31)
Она мне безразлична!
11% (23)
Я ее обожаю!
3% (6)

Проголосовало: 206
Любая программа стремится занять всю доступную память.
Рейтинг: 4.2/10 (4)
Посмотреть все анекдоты
компьютерный форум
 
eXcode.ru » Статьи » PHP » Трюки