Введение в искусство модульного тестирования в PHP

Метки: веб-разработка, модульное тестирование

Введення в мистецтво модульного тестування в PHP Введення в мистецтво модульного тестування в PHP
An Introduction to the Art of Unit Testing in PHP An Introduction to the Art of Unit Testing in PHP

Введение
Тестирование является существенным аспектом в любом языке программирования. Если вы не тестируете свой исходный код, то как вы можете быть уверенны, что он работает так, как вы ожидаете?
Тестирование исходного кода вручную может проводиться только нерегулярно и
ограниченно. Для регулярного и углубленного тестирования исходного кода, ответом будет написание автоматизированных тестов, которые можно запускать часто. В PHP такие тесты обычно написаны с использованием фреймворка модульного тестирования, фреймворк, который дает возможность протестировать исходные коды любых приложений или библиотек, как отдельные изолированные функциональные модули, как класс или метод. Когда модульное тестирование набрало популярности, оно стало обычной практикой в PHP с библиотеками и фрейморками как Swiftmailer, Zend Framework и Symfony, все они включают модульные тесты покрывающие их исходный код.
Модульное тестирование часто видится как нечто скрытое, задание поглощающее время — что иногда случается! Но цель проведения времени за написанием тестов заключается в том, чтобы улучшить качество исходного кода, значит он имеет меньше абсолютных ошибок, многие из которых обнаруживаются на ранних стадиях, непрерывный процесс тестирования предотвращает изменение поведения старого кода при новых изменениях, а также дает уверенность, что ваш код может быть зависимым. Есть также и другие преимущества, позже мы обсудим их подробнее.

Заблуждения о тестировании
Модульное тестирование, да и остальные формы тестирования, упираются в четыре простые объяснения, которые затрудняют понимание разработчиков.
1.Это поглощение времени и длится слишком долго.
2.Целостный код не может быть протестирован.
3.Я не нуждаюсь в написании тестов, так как это все долго работает.
4.Тестирование скучное.
Это заблуждения о тестировании, причины, которые звучат убедительно, но на самом деле они неверны в тонкостях. Ну что же, давайте развеем некоторые из них!
Тестирование только отнимает время. Вопрос в том, почему бы не использовать это время более целесообразно и ответом будет, что это снижает затраченное время в будущем, которое понадобится для модификации, поддержки, рефакторинга и устранения не найденных ошибок в коде. И мы все прекрасно понимаем, что будут тонны всяких не обнаруженных ошибок, если не проводить тестирование тщательно!
Раннее тестирование это как ловля пресловутого червя, также как вы пишете код, вы можете использовать модульные тесты для тестирования изолированных методов/классов или групп функциональных классов, непосредственно. Делая так, вы находите и исправляете ошибки настолько же быстро, как только они успевают появляться. Проблема в том, что вы находите ошибки настолько часто и исправляете их настолько быстро, что вы едва ли замечаете то время, которое они отнимают.
Когда позже вы делаете изменения и тесты проваливаются, вы можете исправить внутреннюю проблему как можно быстрее — больше сохраненного времени едва заметно. Суммарная выгода может быть не заметна и вы можете не заметить ее вовсе — и только видеть, что тестирующий код забирает несколько часов на его написание, а не ошибки, которые были исправлены вами за 10 секунд, что могли исправляться бы несколько минут, часов, 6 месяцев и т.д.
Во-вторых, это запутанный код — и этот запутанный код делается из меньших практических частей. В ООП простая задача часто является «тестируемой». Это как лакмусовый тест для качества распределенного кода. Если ваш код не может быть легко протестирован, то это не ошибка Модульного Тестирования, это проблема с написанием практического кода, который был бы гибким и распределенным. Если вы тестировали как вы написали код, вы бы делали упор на распределение классов почти на автомате. Фактический код представляется слишком запутанным для тестирования, это обычный симптом для слишком долгого периода времени перед началом тестирования!
В третьих, рабочий код и рабочий оттестированный код - это два разных зверя. Тесты предлагают безопасную систему, для внесения изменений, рефакторинга, а также внесение новых возможностей приносит меньше головной боли при добавлении, в то время как вопросы интеграции будут обнаружены почти мгновенно. Они также повышают эффективность от новых программистов в вашей команде, которые не знакомы с кодом, можно видеть их ошибки, которые обнаруживаются мгновенно в короткий период времени и, следовательно, приобретается опыт в запуске.
В обоих случаях, если новый код изменяет поведение уже существующего кода, тесты будут выдавать уведомления. Без тестов, некоторое поведение могло было быть немного изменено без предупреждения кого-либо (в конце концов, если вы не тестируете, то как вы можете обнаружить, пока конечный пользователь не вычислит это и не пожалуется!). Не стоит себя ободрять тем, что кое-что работает без тестов — подождите в очереди на вашем публичном баг-трэкере и ожидайте очевидного натиска. Если вам повезет, код будет достаточно прост и, в конечном итоге, отчет об ошибках обнаружит большинство проблем и вы сможете устранить их перед тем, как кто-то будет очень раздражен. Это до сих пор мало для вашей репутации, также как и будущий эффект от еще большего количества не тестированных изменений.
Напоследок, тестирование это не всегда скучно. О, я знаю, что временами это может быть скучно, но обычно скучно именно потому, что тесты пишутся в конце процесса разработки. Если вы часто переключаетесь от тестов к коду и обратно, вы больше делаете и меньше зеваете. Значит, лечение от скуки это простая задача переключения и периодическое тестирование написанного - не нужно попадать в ловушку когда нужно проводить целые дни за написанием тестов и только тестов.
Значит, больше никаких отговорок. Присядьте, расслабьтесь, читайте далее.

Модульное Тестирование в PHP
Модульное тестирование в PHP может быть представлено тремя способами. Два обычных варианта это SimpleTest Маркуса Бэйкера и PHPUnit Себастьяна Бергманна. Чтобы внести немного замешательства, также есть старый надежный: phpt.
Все три метода позволяют вам производить модульное тестирование кода, а также два фрейморка, предлагающих множество расширений. Мы возьмем пример с использованием PHPUnit. Для установки PHPUnit, можете ознакомится с соответствующим разделом документации (PHPUnit Pocket Guide). Также вам необходимо будет установить PEAR.
PHPUnit (подобно SimpleTest) организовывает тесты в случаи; проще говоря, класс публичные методы которого являются едиными независимыми тестами. Здесь мы собираемся создать класс для представления флота (простая модель).
Кажется через чур просто, но давайте все же двигаться не спеша. Это случай теста, который я написал для поднятия этого класса на какой-то минимальный уровень, перед тем как мы примемся за ввод в базу данных.
Убрать подсветку кода
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php
require_once 'PHPUnit/Framework.php';
require_once 'My/Fleet.php';
 
class MyFleetTest extends PHPUnit_Framework_TestCase
{
    protected $_fleet = null;
 
    public function setUp()
    {
        $this->_fleet = new My_Fleet;
    }
 
    public function tearDown()
    {
        unset($this->_fleet);
    }
 
    public function testShouldNotHaveAnyShipsYetInIntitialState()
    {
        /**
         * $this->_fleet->count() это скучно; давайте опишем SPL Countable интерфейс
         */
        $this->assertEquals(0, count($this->_fleet));
    }
 
    public function testAddingAShipWillIncrementCountByOne()
    {
        $this->_fleet->addShip('USS Enterprise');
        $this->assertEquals(1, count($this->_fleet));
    }
 
    public function testAfterAddingAShipWeCanRetrieveItsNameByIndex()
    {
        $this->_fleet->addShip('USS Enterprise');
        $this->assertEquals('USS Enterprise', $this->_fleet->getShip( count($this->_fleet) - 1 ));
    }
}

Рассмотренные тестовые случаи это хорошее маленькое начало. Мы описали пару методов setUp() и tearDown(). И в SimpleTest и в PHPUnit, они используются для создания фикстур (fixtures). Фикстура - это некий ресурс, содержащий все тесты, которые имеют общее в условии теста и которые устанавливают контекст теста (тестируемую ситуацию) таким же, как и другие повторяющиеся объекты или ресурсы. Так как каждый тест должен быть изолирован (не может делиться информацией), эти методы будут говорить фрейморку создавать наши фикстуры (сущность My_Fleet) перед каждым тестом, и уничтожать их после. Значит, каждый тест будет получать новенькую версию для того чтобы играть с ней, лишенную всего того, что могли сделать тесты, которые выполнялись ранее. Все вышеуказанные тесты являются публичными метода, имена которых начинается с «test». Все что запускается без «test» не будет включено в отчет (если тесты запускаются под PHPUnit). Вы можете использовать это для создания хелпер методов для настроек тестов, не боясь при этом, что данные методы будут расценены фрейморвком как отдельные тесты. Хелпер методы это довольно удобно для повторяющихся задач, так как даже тесты могут поддаваться рефакторингу. Имя класса, по договоренности, заканчивается на «Test». Внутри каждого теста мы имеем обращение к методам «ассерт». Ассерты просто берут ожидаемые значения и сравнивают их с тем, которые были получены. Если ожидаемые значения не совпадают с фактическими, тогда тест будет провален. Знание про ассерты в ваших тестах - это уже 90% от всего дела. Остальные 10% - это уверенность, что каждый тест полностью изолирован от других (используйте фикстуры и имитационные объекты, доступные инструменты для этого). Я использую только вышеупомянутый assertEquals(), но полный список для PHPUnit доступен здесь.
Сейчас мы не рассматриваем имитационные (mock) объекты, но этот компонент очень важен в построении изолированности модульного теста от всех других классов и ресурсов, кроме тестируемого.

Запуск тестов
Запуск тестов PHPUnit может быть осуществлен из консоли (даже MS-DOS на Windows Vista) или веб-браузере. Чтобы сделать это просто, мы остановимся на быстром запуске команд через консоль, где мы храним этот класс теста. PHPUnit также описывает методы организации тестов в блоки, которые не рассматриваются здесь, но которые могут быть очень необходимы, если вы не хотите запускать все тесты один за другим целую вечность.
Для запуска нашего единственного класса теста, откройте консоль и перейдите в место, где хранится тест. Консольная команда простая:
phpunit MyFleetTest
Имя теста будет отражено в имени сохраненного файла, значит класс MyFleetTest должен будет сохранен как файл MyFleetTest.php. Вперед, запускайте тест прямо сейчас. Я заранее предупреждаю вас, что он будет, естественно, провален, потому как мы до сих пор не получили указания по коду класса! Но сейчас все будет...

Модульное тестирование и разработка через тестирование
Возможно, вы уже заметили эту небольшую проблему, существующую на данный момент. Мы написали некоторые тесты. Но где же тестируется фактический класс? Можно с уверенностью сказать, что мы могли бы написать класс и впоследствии тестировать его (вполне нормальная практика в PHP), но для более широкой перспективы, я не стал этого делать.
Один популярный способ использования модульного тестирования - это практика разработки через тестирование (TDD). TDD это необязательный компонент модульного тестирования, скорее это дополнительная практика, которую использует модульное тестирование с тех пор как это доступный стандарт для написания запускаемых примеров. Идея, которая стоит за TDD, состоит в описании поведения классов в запускаемом коде (то есть тестах) перед написанием кода, это предусматривает инструмент для управления тем, как класс должен быть оформлен. Это положило начало для клича «Тесты в первую очередь». И, значит, к популяции «зараженных тестом». Им стоит снять фильм ужасов как-то.
Судя по тестам, которые мы только что написали. В написании тестов до написания кода, мы были вынуждены сделать несколько вещей. В первую очередь, мы придумали открытое API классу перед всем остальным. Следует отметить, в модульном тестировании и TDD, бессмысленно тестировать приватные методы (называемые "Testing State"), так как мы реже интересуемся тем как код что-то сделал, по сравнению с тем каков должен быть конечный результат (ориентируемся на результаты, а не на промежуточные состояния). В куче кода результат остается тем же, но рабочий код со временем будет увеличиваться, в соответствии с рефакторингом, новой функциональностью PHP или в зависимости от требований системы — значит, тестирование всех этих вещей только заставляет нас постоянно переписывать наши тесты по неуважительным причинам.
Теперь, определив открытое API, мы находимся на ранней стадии проектирования. Мы фактически не тестируем на себе (у нас ведь до сих пор нет кода!), вместо этого мы описываем наши ожидания о том, как класс должен работать, и затем пишем код, который работает как мы описывали.
Когда-то мы придем к фактическому написанию кода, и уже будем иметь всю проделанную работу по проектированию и настройке тестов, которые в дальнейшем будут проверять новый код, который мы пишем. Значит, пока TDD является методом проектирования, он по прежнему предоставляет хороший набор тестов. Итак, давайте все-таки напишем наш класс.
Убрать подсветку кода
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
 
class My_Fleet implements Countable
{
    protected $_ships = array();
 
    public function addShip($shipName)
    {
        $this->_ships[] = $shipName;
    }
 
    public function count()
    {
        return count($this->_ships);
    }
 
    public function getShip($index)
    {
        return $this->_ships[ intval($index) ];
    }
}

Работающий класс и все наши тесты пройдут успешно! Возможно, позже мы изменим тесты и позволим объектам Ship, которые описывают __toString(), выводить имя корабля. Небольшое изменение в тестах в strval() возвращаемого значения getShip() и наши тесты будут обновлены для этого измененного поведения.

Оригинал: An Introduction to the Art of Unit Testing in PHP

Рейтинг: 12345   << Вы можете поставить оценку этой статье


Подобные статьи:
   6 инструментов чтобы быть эффективным Web-разработчиком
   Понимание области видимости в объектно-ориентированном JavaScript
   Интеграция FCKeditor в Zend_Form
   Автоматизированное тестирование с использованием Zend Framework
   Паттерн кэширования для моделей


Обсуждение статьи:

 
Den Golotyuk [2009-06-07]
Отличная статья!
Я думаю тут стоило бы упомянуть еще и о другом фреймфорке для тестирования в PHP, а именно

Simpletest: http://www.simpletest.org/


 

Оставить комментарий:

Имя


E-mail


Сообщение