Давайте начнем с признания: вы не пишете тесты. Вы знаете, что должны, но вы не. Я не могу винить вас за это — есть много общих проблем, которые мешают разработчикам писать тесты.
Одно заблуждение, однако, заключается в том, что модульное тестирование не имеет значения. Вот как это обычно происходит: разработчик думает: «Мне нужно выполнить модульные тесты, и я должен использовать PHPUnit, потому что это стандарт. Я не знаю много об этом, однако. ”Затем он посещает сайт PHPUnit и читает первую главу документации, затем вторую, затем третью… и остается почесать голову. «Все ли пишут калькуляторы на PHP? Я разрабатываю веб-приложения. Почему мне показывают, как тестировать калькуляторы? »Что будет дальше? Разработчик закрывает документацию, думая, что тестирование как-то связано с калькуляторами и слишком сложно для его понимания на данный момент.
Может быть, что-то подобное случилось с вами. Возможно, нет. Но вы действительно должны знать, что тестировать и как это тестировать. Такие знания основаны на опыте, поэтому в этой статье я поделюсь своим опытом с модульным тестированием. (Пожалуйста, примите мои извинения заранее, если я немного преувеличиваю и иногда немного неточен; моя цель — помочь вам начать писать тесты, и немного отдувательства помогает делу.)
Функциональное тестирование
Нет проблем с PHPUnit, и нет проблем с профессиональным разработчиком, который решил написать первый тестовый модуль. Существует проблема с предположением, что модульное тестирование — это всегда правильное начало. Какой у тебя проект? Я полагаю, что это веб-приложение, и вы должны проверить его как веб-приложение! Как тестируется веб-приложение? Вы открываете веб-страницу, нажимаете на некоторые ссылки, заполняете форму и видите ожидаемые результаты. Это то, что делают ваши клиенты, так зачем вам начинать тестировать ваше приложение другим способом? Конечно, как разработчик вы можете улучшить тестирование, автоматизировав процесс. И это основная идея приемочного или функционального тестирования.
Википедия говорит это о функциональном тестировании:
Функциональное тестирование — это тип тестирования черного ящика, основанный на тестовых примерах на основе спецификаций тестируемого программного компонента. Функции проверяются путем подачи их на вход и изучения вывода, а внутренняя структура программы редко рассматривается.
Конечно, PHPUnit можно использовать для функционального тестирования. Но PHPUnit не единственный доступный инструмент. Как вы знаете, Selenium IDE широко используется. Мы тоже могли бы это использовать, но давайте посмотрим правде в глаза … мы разработчики PHP и предпочитаем инструменты PHP для тестирования.
Codeception — это современная среда тестирования. Простой приемочный (или функциональный) тест может быть написан с помощью очень простого PHP DSL (предметно-ориентированного языка). Вот пример:
<?php
$I = new WebGuy($scenario);
$I->wantTo("send a feedback");
$I->amOnPage("/");
$I->click("Feedback");
$I->see("Submit your feedback");
$I->fillField("Body","Your site is great!");
$I->click("Submit");
$I->see("Thanks for your feedback!");
Код проверяет, что мы можем отправить отзыв с сайта. Он запускает или эмулирует браузер и выполняет определенные действия на сайте. Все утверждения основаны на содержании страницы. Если мы не увидим текст «Спасибо за ваш отзыв» в последнем ответе, тест не пройден. Просто, правда?
Почему бы не начать с описания всех ваших функций в качестве тестов? Просто напишите наиболее распространенные сценарии использования и выполните их на своем сайте. Функциональное тестирование доказывает, что пользователь может воспроизвести ваши шаги и увидеть те же результаты, и, следовательно, ваше приложение работает хорошо.
В качестве альтернативы вы можете рассмотреть возможность использования других популярных инструментов, таких как PHPUnit и Mink , Behat или Selenium IDE.
Модульное тестирование
Но как насчет юнит-тестов … когда они пригодятся? Давайте начнем с определения, снова из Википедии :
Модульное тестирование — это метод, с помощью которого отдельные блоки исходного кода тестируются, чтобы определить, пригодны ли они для использования. Единица — это самая маленькая тестируемая часть приложения. В процедурном программировании единица может быть целым модулем, но чаще это отдельная функция или процедура. В объектно-ориентированном программировании единица часто представляет собой целый интерфейс, такой как класс, но может быть отдельным методом.
Модульное тестирование — это тестирование вашего кода и ваших внутренних API. При принятии решения о том, когда проводить модульное тестирование, действительно существует одно правило: когда вы пишете свои собственные классы, модули, библиотеки, фреймворки и т. Д., Абсолютно необходимо покрыть их модульными тестами.
Но как насчет «проблемы с калькулятором»? Давайте сформулируем это снова. Мы не пишем калькуляторы, мы строим веб-сайты на основе MVC-фреймворков и CMS. Фреймворки делегируют большинство рутинных задач, и в результате наш код делает не более чем триггерные методы API фреймворка. Но API фреймворка уже протестирован его разработчиками, так почему мы должны тестировать его снова?
То, что вы видите здесь, является проблемой с большинством популярных платформ MVC; просто не существует простого соглашения о том, где хранится бизнес-логика. Если мы хотим протестировать функцию «страница может быть создана», мы должны протестировать контроллер, который берет параметры из формы, которая передает ее в модель, которая проверяет и сохраняет ее. Нет единицы, отвечающей за создание страницы, то есть нет единственной функции или класса, который создает страницу. Чтобы протестировать одну функцию, нам нужно три или более юнит-тестов в разных модулях. Это рационально? Я бы сказал, что это пустая трата времени. Достаточно одного функционального теста, чтобы проверить, что «страница может быть создана».
Если ваши функциональные тесты уже проверяют все, что может сделать пользователь, модульные тесты отлично подходят для тестирования того, что пользователю запрещено делать. Невозможно предсказать все действия, которые пользователь может выполнить на сайте, но в рамках модульных тестов вы можете убедиться, что определенные действия не выполняются — пользователь не сохраняется в базе данных без адреса электронной почты, два пользователя не могут быть созданы с помощью тот же логин и т. д. Напишите модульные тесты, чтобы проверить правила проверки для ваших форм и моделей, правила безопасности для вашего контроллера и так далее.
Существуют различные обстоятельства, которые невозможно воспроизвести с помощью функциональных тестов. Скажем, например, каждый сотый член вашего сайта получает какой-то бонус. Если вы зарегистрируете 100 пользователей вручную, чтобы проверить, кто из них получит бонус? Возможно нет. Модульный тест для такой функции не требуется. Вот вместо этого пример теста PHPUnit:
<?php
class BonusTest extends PHPUnit_Framework_TestCase
{
public function setUp() {
$this->bonus = new Bonus();
}
public function testProvideBonusTo100thUser() {
// let's check there are 99 losers
for ($i=0; $i < 99; $i++) {
$user = new User();
$user->setName(uniqid());
$user->save();
$this->assertFalse($this->bonus->hasBonus($user));
}
// and only one winner
$user = new User();
$user->setName("Winner!");
$user->save();
$this->assertTrue($this->bonus->hasBonus($user));
}
}
В качестве инструмента для модульного тестирования вы можете использовать либо PHPUnit, либо другую современную среду тестирования, такую как Atoum или EnhancePHP . Они разработаны так, чтобы быть менее «предприимчивыми», и поэтому их легче знакомить новичкам. Если вы предпочитаете тестировать в стиле Ruby, проверьте PHPSpec или Spectrum . Кроме того, вышеупомянутая платформа тестирования Codeception может использоваться для тестирования вашего кода аналогичным образом, как и для тестирования вашего сайта.
Резюме
Пишите свои тесты прагматично. Используйте функциональные (приемочные) тесты для тестирования поведения вашего приложения и используйте модульные тесты для тестирования его кода. Важно проверить общее использование, ситуации неправильного использования и редкие события. Но помните, что даже 100% покрытие кода не гарантирует качество или стабильность кода.
Изображение через Джона Квана / Shutterstock