Students Save 30%! Learn & create with unlimited courses & creative assets Students Save 30%! Save Now
Advertisement
  1. Code
  2. Laravel
Code

Laravel и BDD: пишем первую фичу

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called Laravel, BDD And You.
Laravel, BDD and You: Let’s Get Started

Russian (Pусский) translation by Masha Kolesnikova (you can also view the original English article)

Во второй части этой серии под названием Laravel, BDD и Вы мы начнем описывать и строить нашу первую фичу с помощью Behat и PhpSpec. В последней статье мы настроили все необходимое и увидели, как легко мы можем взаимодействовать с Laravel в наших сценариях Behat.

Недавно создатель Behat Константин Кудряшов (a.k.a. everzet) написал действительно замечательную статью под названием «Представление моделирования по примерам». Рабочий процесс, который мы собираемся использовать, был вдохновлен тем, который был представлен everzet.

Короче говоря, мы собираемся .feature для разработки как нашего основного домена, так и нашего пользовательского интерфейса. Я часто чувствовал, что у меня было много дублирования в моих функциях в моих фичах в приемочных/функциональных и интеграционных пакетах. Когда я прочитал предложение Everzet об использовании одной и той же фичи для нескольких контекстов, я наконец понял как нужно делать.

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

Небольшой рефакторинг

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

Во-первых, мы собираемся удалить фичу welcome, которую мы сделали в первой части, так как она нам действительно не нужна, и она не соответствует основному стилю, который нам нужен, чтобы использовать несколько контекстов.

Во-вторых, у нас будут свои фичи в корне папки features, поэтому мы можем продолжить и удалить атрибут path из нашего файла behat.yml. Мы также переименуем LaravelFeatureContext в FunctionalFeatureContext (не забудьте также изменить имя класса):

Наконец, чтобы немного все подчистить, я думаю, что мы должны переместить все связанные с Laravel вещи в отдельный трейт:

В FunctionalFeatureContext мы можем затем использовать этот трейт и удалить то, что мы только что переместили:

Трейты - отличный способ очистить ваши контексты.

Совместное использование фичи

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

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

Хорошо, давайте попробуем Behat создать сценарии для нас:

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

Наш функциональный контекст готов к работе, но нам также нужен контекст для нашего пакета интеграции. Сначала мы добавим пакет в файл behat.yml:

Затем мы можем просто скопировать объект FeatureContext по умолчанию:

Не забудьте изменить имя класса на IntegrationFeatureContext, а также скопировать оператор use для PendingException.

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

Проектирование домена

На этом этапе мы готовы начать заполнять шаги в нашем контексте интеграции, чтобы разработать основной домен нашего приложения. Первый шаг: Given I have the following time entries , за которыми следует таблица с записями времени. Сделаем все просто и перейдем к строкам таблицы, попробуем создать экземпляр времени для каждого из них и добавить их в массив записей в контексте:

Запуск Behat приведет к фатальной ошибке, так как класс TimeTracker\TimeEntry еще не существует. Именно здесь PhpSpec выходит на сцену. В конце концов, TimeEntry станет классом Eloquent, хотя мы пока не беспокоимся об этом. PhpSpec и ORM, такие как Eloquent, не так хорошо сочетаются, но мы все еще можем использовать PhpSpec для генерации класса и даже специфицировать некоторые основные действия. Давайте используем генераторы PhpSpec для генерации класса TimeEntry:

После создания класса нам необходимо обновить раздел автозагрузки нашего файла composer.json:

И, конечно, запустите composer dump-autoload.

Запуск PhpSpec дает нам зеленый цвет. Бегущий Беат дает нам зеленый цвет. Какое прекрасное начало!

Перейдем к следующему шагу: When I generate the time sheet.

Ключевое слово здесь «generate», которое выглядит как термин из нашего домена. В мире программистов перевод «генерировать расписание» в код может означать только создание экземпляра класса TimeSheet с кучей записей времени. При проектировании нашего кода важно попытаться придерживаться языка из домена. Таким образом, наш код поможет описать предполагаемое поведение нашего приложения.

Я определяю термин generate как важный для домена, поэтому я думаю, что у нас должен быть статический метод генерации в классе TimeSheet, который служит псевдонимом для конструктора. Этот метод должен принимать коллекцию записей времени и хранить их в расписании.

Вместо того, чтобы просто использовать массив, я думаю, будет иметь смысл использовать класс Illuminate\Support\Collection, который поставляется с Laravel. Поскольку TimeEntry будет моделью Eloquent, когда мы запрашиваем базу данных для записей времени, мы получим одну из этих коллекций Laravel в любом случае. Как насчет чего-то вроде этого:

Кстати, TimeSheet не будет классом Eloquent. По крайней мере, на данный момент нам нужно только сохранить записи времени, а затем временные листы будут только генерироваться из записей.

Запуск Behat снова приведет к фатальной ошибке, потому что TimeSheet не существует. PhpSpec может помочь нам решить эту проблему:

Мы по-прежнему получаем фатальную ошибку после создания класса, потому что статический метод generate() по-прежнему не существует. Поскольку это действительно простой статический метод, я не думаю, что есть необходимость в спецификации. Это не что иное, как оболочка для конструктора:

Это вернет Behat к зеленому прохождению тестов, но PhpSpec теперь ругается, говоря: Argument 1 passed to TimeTracker\TimeSheet::__construct() must be an instance of Illuminate\Support\Collection, none given. Мы можем решить это, написав простую функцию let(), которая будет вызываться перед каждой спецификацией:

Это вернет нас к зеленому цвету. Функция гарантирует, что временной лист всегда строится с моком класса Collection.

Теперь мы можем безопасно перейти к следующему шагу Then my total time spent on...  Нам нужен метод, который принимает имя задачи и возвращает накопленную продолжительность всех записей с этим именем задачи. Непосредственно переведенный из gherkin в код, это может быть что-то вроде totalTimeSpentOn($task):

Метод не существует, поэтому вызов Behat даст нам вызов неопределенного метода TimeTracker\TimeSheet::totalTimeSpentOn().

Чтобы специфицировать метод, мы напишем спецификацию, которая выглядит как-то похожее на то, что мы уже имеем в нашем сценарии:

Обратите внимание, что мы не используем моки для экземпляров TimeEntry и Collection. Это наш пакет интеграции, и я не думаю, что есть необходимость мотать его. Объекты довольно просты, и мы хотим убедиться, что объекты в нашем домене взаимодействуют, как мы ожидаем. На счет этого существует много мнений, но для меня это имеет смысл.

Двигаемся вперед:

Чтобы отфильтровать записи, мы можем использовать метод filter() в классе Collection. Простое решение, которое дает нам зеленый цвет:

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

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

Запуск Behat даст нам Call to undefined method TimeTracker\TimeSheet::totalTimeSpent(). Вместо того, чтобы делать отдельный пример в нашей спецификации для этого метода, как насчет того, чтобы просто добавить его к тому, который у нас уже есть? Это может быть не совсем в соответствии с тем, как делать «правильно», но давайте быть немного прагматичными:

Пусть PhpSpec генерирует метод:

Получить зеленые тесты теперь легко, когда у нас есть метод sumDuration():

И теперь у нас есть зеленая фича. Наш домен постепенно развивается!

Проектирование пользовательского интерфейса

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

Во время работы в набором тестов мы можем добавить флаг -s, чтобы инструктировать Behat запускать наши фичи только через FunctionalFeatureContext:

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

Запуск Behat даст нам фатальную ошибку Call to undefined method TimeTracker\TimeEntry::save(), поскольку TimeEntry по-прежнему не является моделью Eloquent. Это легко исправить:

Если мы снова запустим Behat, Laravel пожалуется, что он не сможет подключиться к базе данных. Мы можем исправить это, добавив файл database.php в каталог app/config/testing, чтобы добавить сведения о соединении для нашей базы данных. Для более крупных проектов вы, вероятно, захотите использовать один и тот же сервер базы данных для своих тестов и производственного кода, но в нашем случае мы просто используем базу данных в памяти SQLite. Это очень просто настроить с помощью Laravel:

Теперь, если мы запустим Behat, который скажет нам, что нет таблицы time_entries. Чтобы исправить это, нам нужно выполнить миграцию:

Мы все еще не зеленые, так как нам нужен способ научить Behat выполнять наши миграции перед каждым сценарием, поэтому каждый раз мы получаем чистый список. Используя аннотации Behat, мы можем добавить эти два метода к трейту LaravelTrait:

Это довольно аккуратно, и наш первый шаг к зеленому.

Следующий шаг - When I generate the time sheet. Я его вижу следующим образом: создание листа времени - это эквивалент посещения действия index ресурса записи времени, поскольку временной лист - это сбор всех временных записей. Таким образом, объект временного листа похож на контейнер для всех записей времени и дает нам хороший способ обработки записей. Вместо того, чтобы переходить в /time-entries, чтобы увидеть временной лист, я думаю, что работник должен перейти на /time-sheet. Мы должны добавить это в наше определение шага:

Это вызовет исключение NotFoundHttpException, поскольку маршрут еще не определен. Как я только что объяснил, я думаю, что этот URL-адрес должен сопоставляться с действием index на ресурсе записи времени:

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

Готово.

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

Мы ищем тег <td> с идентификатором [task_name]TotalDuration или totalDuration в последнем примере.

Поскольку у нас пока нет представления, сканер сообщает нам, что The current node list is empty.

Чтобы исправить это, построим действие index. Во-первых, мы получаем коллекцию записей времени. Во-вторых, мы создаем временной лист из записей и отправляем его в (все еще несуществующее) отображение.

В настоящее время представление будет состоять из простой таблицы с суммарными значениями продолжительности:

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

Вывод

Если вы запустите vendor / bin / behat, чтобы запустить оба набора Behat, вы увидите, что оба они теперь зеленые. Если вы запустите PhpSpec, то, к сожалению, вы увидите, что наши спецификации сломаны. Мы получаем фатальную ошибку. Класс «Красноречивый» не найден в .... Это потому, что Eloquent является псевдонимом. Если вы посмотрите в app/config/app.php под псевдонимами, вы увидите, что Eloquent на самом деле является псевдонимом для Illuminate\Database\Eloquent\Model. Чтобы вернуть PhpSpec в зеленый цвет, нам нужно импортировать этот класс:

Если вы запустите эти две команды:

Вы увидите, что мы вернулись к зеленому, как с Behat, так и с PhpSpec. Ура!

Мы описали и разработали нашу первую функцию, полностью используя подход BDD. Мы видели, как мы можем извлечь выгоду из разработки основного домена нашего приложения, прежде чем мы будем беспокоиться о пользовательском интерфейсе и конкретном материале. Мы также видели, как легко взаимодействовать с Laravel и особенно с базой данных в наших контекстах Behat.

В следующей статье мы собираемся немного порефакторить, чтобы избежать слишком большой логики для наших моделей Eloquent, поскольку их сложнее тестировать изолированно и они тесно связаны с Laravel. Будьте на связи!

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.