Разработка через тестирование (TDD)

9 декабря 2010 г.

Тестирование кода раздражает, но приносит огромную пользу при работе над проектом. Сегодня мы будем использовать TDD, чтобы писать и тестировать код более эффективно.

Что такое TDD?

С самого начала компьютерной эры программисты и ошибки боролись за превосходство. Это неизбежное явление. Каждый программист становился жертвой ошибок. Код небезопасен. Вот почему мы проводим тесты.

Разработка через тестирование (test-driven development) — техника программирования, при которой модульные тесты для программы или её фрагмента пишутся до самой программы (test-first development) и, по существу, управляют её разработкой.

Как это работает?

Разработка через тестирование или TDD представляет собой короткий повторяющийся цикл развития:

  1. Прежде чем писать любой код, необходимо сначала написать автоматический тест для вашего кода. Во время написания теста необходимо учитывать все возможные входные данные, ошибки и вывод в браузер
  2. После этого можно начать написание кода. Необходимо вносить изменения до тех пор, пока код не будет проходить автоматические тесты
  3. Как только код прошёл тест, можно начинать очистку кода (рефакторинг)

Отлично, но чем это лучше регулярного тестирования?

Вы не проводили тестирование программы по нескольким причинам:

  • вы чувствовали, что это пустая трата времени, так как были незначительное поправки кода
  • у вас не было достаточно времени для теста, так как менеджер проекта хотел получить продукт как можно быстрее
  • вы откладывали это на завтра

Долгое время ничего не происходит, и вы перенесли ваш продукт на производство. Но иногда после того как продукт перенесён на производство, всё происходит не так.

Блогун - монетизируем блоги
TDD ликвидирует наши ошибки. Когда программа была разработана с использованием разработки через тестирование, она позволяет вносить изменения и проводить тесты более эффективно и быстро. Всё что нам нужно сделать – это запустить тесты. Если код проходит все тесты, значит его можно применять, значит мы внесли ошибки вместе с изменениями. Зная, какая часть испытаний провалилась, легче ликвидировать ошибки.

Как провести тесты?

Существует много фреймворков для тестирования PHP скриптов. Одним из наиболее широко используемых является PHPUnit.

PHPUnit это отличный фреймворк для тестирование, который можно легко интегрировать в свои проекты или другие проекты, созданные с помощью популярных PHP фреймворков.

В нашем примере мы не будет нуждаться во множестве функций, которые предоставляет PHPUnit. Поэтому мы будем использовать более простую систему тестирования, названную SimpleTest.

Рассмотрим разработку и тестирование гостевой книги, в которой каждый пользователь может добавить, а также просматривать сообщения. Предположим, что разметка уже сделана, и мы просто делаем класс, который содержит логику гостевой книги, которая включает в себя чтение и запись из базы данных. Метод чтения этого класса – это то, что мы собираемся разработать и протестировать.

Шаг 1. Настройка SimpleTest

Загрузите SimpleTest здесь и распакуйте в любую папку. Всё должно выглядеть так:

Index.php будет подключать guestbook.php, и вызывать метод вывода записей. В папке classes будет находиться guestbook.php, а в папке test находится распакованный SimpleTest.

Шаг 2. Планируйте свои действия

Второй шаг является самым важным, так как именно в нём мы начнём создавать наш тест. Для этого вам необходимо запланировать то, что ваша функция будет делать, какие входные данные получит, а какие вернёт скрипту. Этот шаг похож на игру в шахматы – вам необходимо знать всё о своём противнике (программе), включая все его слабости (возможные ошибки) и силы (что происходит, если программа успешно работает).

Давайте установим схему работы для нашей гостевой книги:

  • у этой функции не существует входных данных, так как она выберет все данные из гостевой книги и вернёт их скрипту
  • она возвратит массив записей гостевой книги, включая имя автора и его сообщение. Если записи отсутствуют – пустой массив
  • если будут записи, возвращается массив с одним или более записями
  • в то же время, у массива будет структура, похожая на это
    Array (
    [0] => Array (['name'] = "Bob", ['message'] = "Hi, I’m Bob."),
    [1] => Array (['name'] = "Tom", ['message'] = "Hi, I'm Tom.")
    )

Шаг 3. Напишите тест

Теперь мы можем написать первый тест. Для начала создадим файл с именем guestbook_test.php в папке test.

<?
require_once(dirname(__FILE__) . '/simpletest/autorun.php');
require_once('../classes/guestbook.php');
class TestGuestbook extends UnitTestCase {
}
?>

Затем, давайте реализуем наши требование из шага 2.

<?
require_once(dirname(__FILE__) . '/ autorun.php');
require_once('../classes/guestbook.php');
class TestGuestbook extends UnitTestCase {
function testViewGuestbookWithEntries()
{
$guestbook = new Guestbook();
// Добавляем новую запись
$guestbook->add("Bob", "Hi, I'm Bob.");
$guestbook->add("Tom", "Hi, I'm Tom.");
$entries = $guestbook->viewAll();
$count_is_greater_than_zero = (count($entries) > 0);
$this->assertTrue($count_is_greater_than_zero);
$this->assertIsA($entries, 'array');
foreach($entries as $entry) {
$this->assertIsA($entry, 'array');
$this->assertTrue(isset($entry['name']));
$this->assertTrue(isset($entry['message']));
}
}
function testViewGuestbookWithNoEntries()
{
$guestbook = new Guestbook();
$guestbook->deleteAll(); // Удаляем все записи
$entries = $guestbook->viewAll();
$this->assertEqual($entries, array());
}
}

Как вы можете здесь видеть, мы проверяем вывод гостевой книги с записями и без. Мы проверяем удовлетворяют ли эти две функции критериям, установленным в шаге 2. Каждая из наших испытательных функций начинается со слова ‘test’. Мы сделали это для того, потому что SimpleTest при запуске этого класса будет искать функции начинающиеся со слова ‘test’ и запускать их.

В нашем испытательном классе мы использовали некоторые методы проверки: assertTrue, assertIsA, и assertEquals. Функция assertTrue проверяет, является ли значение истинным. assertIsA проверяет тип переменной. И наконец, функция assertEquals проверяет равна ли переменная определённому значению.

Есть другие методы проверки, предоставляемые SimpleTest:

assertTrue($x)Неудача, если $x == false
assertFalse($x)Неудача, если $x = = true
assertNull($x)Неудача, если $x установлен
assertNotNull($x)Неудача, если $x не установлен
assertIsA($x, $t)Неудача, если $x не принадлежит к типу $t
assertNotA($x, $t)Неудача, если $x принадлежит к типу $t
assertEqual($x, $y)Неудача, если $x == $y это false
assertNotEqual($x, $y)Неудача, если $x == $y это true
assertIdentical($x, $y)Неудача, если $x == $y это false, или не совпадают типы
assertNotIdentical($x, $y)Неудача, если $x == $y это true, или совпадают типы
assertReference($x, $y)Неудача, если $x и $y копии
assertClone($x, $y)Неудача, если $x и $y не копии
assertPattern($p, $x)Неудача, если регулярное выражение $p не соответствует $x
assertNoPattern($p, $x)Неудача, если регулярное выражение $p соответствует $

Шаг 4. Проигрыш

После того как вы закончили писать код, вы запускаете тест. При первом запуске тест должен провалиться. Если этого не произошло, то ваш тест ничего не проверяет.

Чтобы запустить тест, вы должны открыть guestbook_test.php в браузере. Вы увидите:

Так как мы ещё не создали наш класс guestbook, мы потерпели провал. Чтобы это исправить создайте файл guestbook.php в папке classes. Класс будет содержать методы которые мы планировали, но они ничего содержать не будут. Помните, мы пишем тесты раньше, чем написать код продукта.
Так как мы ещё не создали наш класс guestbook, мы потерпели провал. Чтобы это исправить создайте файл guestbook.php в папке classes. Класс будет содержать методы которые мы планировали, но они ничего содержать не будут. Помните, мы пишем тесты раньше, чем написать код продукта.

<?
class Guestbook
{
public function viewAll() {}
public function add( $name, $message ) {}
public function deleteAll() {}
}
?>

Когда вы запустить тест следующий раз, вы увидите:

class Guestbook
{
// Для того чтобы сохранить время и не создавать таблице мы
// симулируем таблицу
// Мы имеем две записи в таблице
private static $_entries = array(
array ('name' => 'Kirk','message' => 'Hi, I\'m Kirk.'),
array ('name' => 'Ted','message' => 'Hi, I\'m Ted.'));
public function viewAll() {
return self::$_entries; // Возвращаем взе записи из таблицы
}
public function add( $name, $message ) {
// Здесь мы симулируем запись в таблицу, добавлением новой записи в массив
// Правильный вариант: self::$_entries[] = array('name' => $name, 'message' => $message );
self::$_entries[] = array('notname' => $name, 'notmessage' => $message ); //упс, где-то здесь ошибка
return true;
}
public function deleteAll() {
self::$_entries = array(); // Моделируем удаление записей
return true;
}
}
?>

Когда вы запустить тест следующий раз, вы увидите:

Как мы видим, наш код тест всё ещё не проходит, исправлением этого мы займёмся на следующем шаге.

Шаг 5. Написание кода продукта

Теперь, когда у нас есть тест, мы можем приступать к написанию кода. Откройте файл guestbook.php и начните писать:

class Guestbook
{
// Для того чтобы сохранить время и не создавать таблице мы
// симулируем таблицу
// Мы имеем две записи в таблице
private static $_entries = array(
array ('name' => 'Kirk','message' => 'Hi, I\'m Kirk.'),
array ('name' => 'Ted','message' => 'Hi, I\'m Ted.'));
public function viewAll() {
return self::$_entries; // Возвращаем взе записи из таблицы
}
public function add( $name, $message ) {
// Здесь мы симулируем запись в таблицу, добавлением новой записи в массив
// Правильный вариант: self::$_entries[] = array('name' => $name, 'message' => $message );
self::$_entries[] = array('notname' => $name, 'notmessage' => $message ); //упс, где-то здесь ошибка
return true;
}
public function deleteAll() {
self::$_entries = array(); // Моделируем удаление записей
return true;
}
}
?>

В классе guestbook.php есть некоторые ошибки, которые мы допустили преднамеренно, теперь мы проверим проходит наш код тест или нет:

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

$this->assertTrue(isset($entry['name']));
$this->assertTrue(isset($entry['message']));

Это говорит нам, что возвращаемый массив не имел правильного ключа. Основываясь на этом, мы знаем какая часть нашего кода работает неверно:

public function add( $name, $message ) {
// Здесь мы симулируем запись в таблицу, добавлением новой записи в массив
self::$_entries[] = array('name' => $name, 'message' => $message ); //исправлено!
return true;
}

Сейчас, когда запустим тест, мы увидим:

Шаг 6. Рефакторинг и совершенствование кода

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

Шаг 7. Повторите

В конечном счёте, когда необходимы новые функции, вы должны будете написать новые тесты. Это легко! Повторите все шаги, начиная с шага 2 (так как ваши файл SimpleTest должны быть уже настроены), и начните цикл снова и снова.

Заключение

Мы рассмотрели использование SimpleTest для разработки продуктов. Мы охватили не весь функционал SimpleTest. Дополнительную информацию можно найти, используя документацию (англ.) или статью Википедии (англ.).

Тестирование – неотъемлемая часть цикла развития, но обычно, это первая вещь, которую сокращают при сжатых сроках разработки. Хотелось бы надеяться, что после прочтения этой статьи, вы будите ценить разработку через тестирование.

  • Похожие статьи
  • Предыдущие из рубрики