Части
перейти к части...
Разработка через тестирование (Test-Driven Development, TDD) - процесс разработки приложений. Если в кратце, то по методу TDD вы сначала пишете тесты, а затем код - не наоборот. Давайте рассмотрим процесс более подробно.
-
Прежде, чем вы напишете хоть одну строчку реального кода, вам нужно написать тест. Это как расписать новый функционал на бумаге сразу после того, как вы его придумали. Что-то вроде "Я хочу иметь возможность сделать GET запрос на '/blog/posts' и получить в ответе от сервера список постов блога постранично.", но более детально и конкретно.
-
Итак, у вас есть тест, но, разумеется, он не проходит проверку, так как у нас еще нет реального кода. На этом этапе вы начинаете писать код достаточный для того, чтобы ваш тест прошел успешно. Этот этап как создание прототипа - главное, чтобы работало как нужно, а сама реализация уже не так важна.
-
Далее можете приступить к рефакторингу кода. Запускайте тест после каждого изменения в коде и смотрите, чтобы тест не начал выдавать ошибок. Остановитесь тогда, когда качество кода вас удовлетворяет. Переходите к следующему функционалу.
Если вы все еще не видите преимуществ TDD - может вас переубедят аргументы ниже.
Вам наверняка будет лень писать тесты на существующий код, который вроде и так работает. С подходом же TDD вы начинаете с тестов. В результате бОльшая часть вашего кода покрыта тестами изначально, и, скорее всего, покрыта лучше, т.к. весь функционал был реализован в соответствие с тестами, а не наоборот.
Помимо этого, наличие тестов спасает вас от той ситуации (которая возникает чаще, чем нам бы хотелось), когда вы редактируете строку кода в одном месте, и что-нибудь ломается в совершенно другом, неожиданном и неочевидном месте, и вы узнаете об этом совершенно случайно спустя несколько часов/дней/недель/месяцев. При наличии тестов вам достаточно лишь прогнать все тесты после изменения в коде и все, что было случайно сломано, тут же всплывет на поверхность.
Подготовка
Мы будем учиться на приктике, так что создайте новый проект Laravel (на момент написания этого урока последния версия - 5.6). PHPUnit входит в комплект Laravel из коробки, но вы также можете установить его на своей машине глобально, выполнив следующую команду в терминале:
composer global require phpunit/phpunit
Вместе с Laravel также идет файл конфигурации PHPUnit phpunit.xml
и папки для хранения тестов с двумя демо-классами:
- tests
- Feature
- ExampleTest.php
- Unit
- ExampleTest.php
- CreatesApplication.php
- TestCase.php
Trait CreatesApplication.php
подключает приложение Laravel в тестах и используется в классе TestCase.php
.
Класс TestCase.php
- это базовый класс, от которого наследуются все тесты в Laravel проекте. Этот класс, в свою очередь, наследуется от другого класса Laravel, который наследуется от класса TestCase
из PHPUnit.
Две папки, "Feature" и "Unit", служат для хранения функциональных и модульных (юнит) тестов соответственно. Какой-то реальной разницы между двумя нет. Модульные тесты следует использовать для тестирования отдельных классов (модулей), а функциональные тесты - для тестирования более комплексных вещей.
Прежде, чем мы двинемся дальше, убедитесь, что PHPUnit работает и оба стандартных теста проходят успешно. Выполните команду vendor/bin/phpunit
(или просто phpunit
, если вы установили PHPUnit глобально) из корня вашего проекта на Laravel. Вы должны увидеть что-то похожее на это:
PHPUnit 7.0.2 by Sebastian Bergmann and contributors.
.. 2 / 2 (100%)
Time: 79 ms, Memory: 10.00MB
OK (2 tests, 2 assertions)
Обращаю ваше внимание на то, что со временем тестов в вашем проекте станет намного больше и прогонять весь набор каждый раз - медленно и необязательно. Вместо этого вы можете выполнять тесты только из одного выбранного тестового класса, а можете вообще выполнять единственный тестовый метод. Для этого выполните phpunit --filter
с названием тестового класса или метода через пробел, например phpunit --filter ExampleTest
или phpunit --filter testExample
.
С этим разобрались, теперь удалить оба файла ExampleTest.php
- они нам больше не пригодятся.
Подготовка базы данных
Чтобы продемонстрировать процесс разработки по методу TDD мы разработаем простой модуль новостей с CRUD. Но сначала давайте подготовим базу данных. Создайте модель со связанной миграцией, выполнив следующую команду:
php artisan make:model News -m
У каждой новости будет заголовок (title), текст (body) и изображение, которое будет выводиться вверху статьи. Вот как должен выглядеть файл миграции:
Schema::create('news', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->string('body');
$table->string('image_path')->nullable();
$table->timestamps();
});
Теперь создайте ваш первый тест, в котором мы будем тестировать создание новой новости:
php artisan make:test CreateNewsTest
Эта команда создаст файл tests/Feature/CreateNewsTest.php
. Внутри файла будет демо-тест, который должен пройти успешно, если вы выполните команду phpunit
.
Название каждого тестового файла и класса должно заканчиваться на "Test". Название каждого тестового метода должно начинаться с "test". Если вы хотите называть методы как-то по-другому, добавьте к ним аннотацию /** @test */
. Именно это мы и будем делать, чтобы названия тестов были более наглядными.
Откройте файл с тестом. Вверху вы заметите импорт use Illuminate\Foundation\Testing\RefreshDatabase;
. Мы будем использовать этот trait, поэтому в начале класса добавьте use RefreshDatabase;
.
class CreateNewsTest extends TestCase
{
use RefreshDatabase;
...
}
Этот trait отменяет все изменения, которые были сделаны в БД во время выполнения тестового метода, так что к моменту начала выполнения следующего тестового метода БД будет находиться в изначальном состоянии. Благодаря этому данные из разных тестов не влияют на результаты друг друга и порядок, в котором выполняются тестовые методы, не имеет значения.
Если вы попробуете запустить тесты сейчас, они не пройдут, так как мы еще не настроили соединение с БД в файле .env
. Вместо этого мы укажем настройки БД в файле phpunit.xml
. Откройте его и переместитесь в конец в раздел <php>
. В этом блоке хранятся переменные среды, которые используются только во время выполнения PHPUnit тестов. Здесь можно перезаписать любые значения из файла .env
.
Для наших тестов мы будем использовать базу данных SQLite в оперативной памяти. Так как данные хранятся в оперативной памяти, БД работает невероятно быстро (по сравнению со, скажем, MySQL). Все данные удаляются после того, как тесты завершены. Также, поскольку это отдельная от основной базы проекта БД, тесты не попортят ваши реальные данные.
Единственные минус здесь в том, что синтаксис и поведение SQLite немного отличается от синтаксиса и поведения реальной СУБД, которую вы, скорее всего, будете использовать в проекте - MySQL, PostgreSQL, или любой другой SQL СУБД. В большинстве случае не о чем беспокоиться, но иногда будут моменты, когда реальный код будет работать как надо, а тесты проходить не будут. Просто помните об этом, к тому же часто можно придумать какой-нибудь выход из подобных ситуаций.
Итак, добавьте следующие строки в блок php
в файле phpunit.xml
:
<php>
...
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
</php>
Если вы выполните команду phpunit
сейчас, тесты должны пройти успешно (если конечно у вас на машине установлен SQLite, иначе вам придется сначала установить его - загуглите как это сделать, это не сложно). Итак, мы готовы приступить к реальной разработке.
Все материалы на сайте voerro абсолютно бесплатны и написаны автором в свободное от основной работы время. Если уроки сайта оказались для вас полезными, пожалуйста, помогите проекту. Спасибо!