Статьи

Laravel, BDD and You: первая особенность

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

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

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

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

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

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

1
$ git rm features/functional/welcome.feature

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

1
2
3
4
default:
   suites:
       functional:
           contexts: [ FunctionalFeatureContext ]

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

01
02
03
04
05
06
07
08
09
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
# features/bootstrap/LaravelTrait.php
 
<?php
 
use Illuminate\Foundation\Testing\ApplicationTrait;
 
trait LaravelTrait
{
    /**
     * Responsible for providing a Laravel app instance.
     */
    use ApplicationTrait;
 
    /**
     * @BeforeScenario
     */
    public function setUp()
    {
        if ( ! $this->app)
        {
            $this->refreshApplication();
        }
    }
 
    /**
     * Creates the application.
     *
     * @return \Symfony\Component\HttpKernel\HttpKernelInterface
     */
    public function createApplication()
    {
        $unitTesting = true;
 
        $testEnvironment = ‘testing’;
 
        return require __DIR__.’/../../bootstrap/start.php’;
    }
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
/**
 * Behat context class.
 */
class FunctionalFeatureContext implements SnippetAcceptingContext
{
    use LaravelTrait;
 
    /**
     * Initializes context.
     *
     * Every scenario gets its own context object.
     * You can also pass arbitrary arguments to the context constructor through behat.yml.
     */
    public function __construct()
    {
    }

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

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

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

Хорошо, давайте сделаем так, чтобы Behat сгенерировал для нас сценарии

1
$ vendor/bin/behat —dry-run —append-snippets

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
 * @Given I have the following time entries
 */
public function iHaveTheFollowingTimeEntries(TableNode $table)
{
    throw new PendingException();
}
 
/**
 * @When I generate the time sheet
 */
public function iGenerateTheTimeSheet()
{
    throw new PendingException();
}
 
/**
 * @Then my total time spent on :task should be :expectedDuration minutes
 */
public function myTotalTimeSpentOnTaskShouldBeMinutes($task, $expectedDuration)
{
    throw new PendingException();
}
 
/**
 * @Then my total time spent should be :expectedDuration minutes
 */
public function myTotalTimeSpentShouldBeMinutes($expectedDuration)
{
    throw new PendingException();
}

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

Далее мы можем просто скопировать стандартный FeatureContext :

1
$ cp features/bootstrap/FeatureContext.php features/bootstrap/IntegrationFeatureContext.php

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

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
use TimeTracker\TimeEntry;
 
 
/**
 * @Given I have the following time entries
 */
public function iHaveTheFollowingTimeEntries(TableNode $table)
{
    $this->entries = [];
 
    $rows = $table->getHash();
 
    foreach ($rows as $row) {
        $entry = new TimeEntry;
 
        $entry->task = $row[‘task’];
        $entry->duration = $row[‘duration’];
 
        $this->entries[] = $entry;
    }
}

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

1
2
3
$ vendor/bin/phpspec desc «TimeTracker\TimeEntry»
$ vendor/bin/phpspec run
Do you want me to create `TimeTracker\TimeEntry` for you?

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

01
02
03
04
05
06
07
08
09
10
11
12
«autoload»: {
    «classmap»: [
        «app/commands»,
        «app/controllers»,
        «app/models»,
        «app/database/migrations»,
        «app/database/seeds»
    ],
    «psr-4»: {
        «TimeTracker\\»: «src/TimeTracker»
    }
},

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

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

Позволяя Behat направлять наш путь, как насчет того, чтобы мы просто перешли к следующему шагу, When I generate the time sheet рабочего When I generate the time sheet ?

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

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
use Illuminate\Support\Collection;
use TimeTracker\TimeSheet;
use TimeTracker\TimeEntry;
 
 
/**
 * @When I generate the time sheet
 */
public function iGenerateTheTimeSheet()
{
    $this->sheet = TimeSheet::generate(Collection::make($this->entries));
}

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

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

1
2
3
4
5
6
7
8
$ vendor/bin/phpspec desc «TimeTracker\TimeSheet»
$ vendor/bin/phpspec run
Do you want me to create `TimeTracker\TimeSheet` for you?
$ vendor/bin/phpspec run
 
$ vendor/bin/behat
 
PHP Fatal error: Call to undefined method TimeTracker\TimeSheet::generate()

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
<?php
 
namespace TimeTracker;
 
use Illuminate\Support\Collection;
 
class TimeSheet
{
    protected $entries;
 
    public function __construct(Collection $entries)
    {
        $this->entries = $entries;
    }
 
    public static function generate(Collection $entries)
    {
        return new static($entries);
    }
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
 
namespace spec\TimeTracker;
 
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
 
use Illuminate\Support\Collection;
use TimeTracker\TimeEntry;
 
class TimeSheetSpec extends ObjectBehavior
{
    function let(Collection $entries)
    {
        $entries->put(new TimeEntry);
 
        $this->beConstructedWith($entries);
    }
 
    function it_is_initializable()
    {
        $this->shouldHaveType(‘TimeTracker\TimeSheet’);
    }
}

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

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

1
2
3
4
5
6
7
8
9
/**
* @Then my total time spent on :task should be :expectedDuration minutes
*/
public function myTotalTimeSpentOnTaskShouldBeMinutes($task, $expectedDuration)
{
    $actualDuration = $this->sheet->totalTimeSpentOn($task);
 
    PHPUnit::assertEquals($expectedDuration, $actualDuration);
}

Метод не существует, поэтому запуск Behat даст нам Call to undefined method TimeTracker\TimeSheet::totalTimeSpentOn() .

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
function it_should_calculate_total_time_spent_on_task()
{
    $entry1 = new TimeEntry;
    $entry1->task = ‘sleeping’;
    $entry1->duration = 120;
 
    $entry2 = new TimeEntry;
    $entry2->task = ‘eating’;
    $entry2->duration = 60;
 
    $entry3 = new TimeEntry;
    $entry3->task = ‘sleeping’;
    $entry3->duration = 120;
 
    $collection = Collection::make([$entry1, $entry2, $entry3]);
 
    $this->beConstructedWith($collection);
 
    $this->totalTimeSpentOn(‘sleeping’)->shouldBe(240);
    $this->totalTimeSpentOn(‘eating’)->shouldBe(60);
}

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

Двигаясь вдоль:

1
2
3
4
5
6
7
$ vendor/bin/phpspec run
Do you want me to create `TimeTracker\TimeSheet::totalTimeSpentOn()` for you?
 
$ vendor/bin/phpspec run
 
  25 ✘ it should calculate total time spent on task
      expected [integer:240], but got null.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public function totalTimeSpentOn($task)
{
    $entries = $this->entries->filter(function($entry) use ($task)
    {
        return $entry->task === $task;
    });
 
    $duration = 0;
 
    foreach ($entries as $entry) {
        $duration += $entry->duration;
    }
 
    return $duration;
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
public function totalTimeSpentOn($task)
{
    $entries = $this->entries->filter(function($entry) use ($task)
    {
        return $entry->task === $task;
    });
 
    return $this->sumDuration($entries);
}
 
protected function sumDuration($entries)
{
    $duration = 0;
 
    foreach ($entries as $entry) {
        $duration += $entry->duration;
    }
 
    return $duration;
}

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

1
2
3
4
5
6
7
8
9
/**
 * @Then my total time spent should be :expectedDuration minutes
 */
public function myTotalTimeSpentShouldBeMinutes($expectedDuration)
{
    $actualDuration = $this->sheet->totalTimeSpent();
 
    PHPUnit::assertEquals($expectedDuration, $actualDuration);
}

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

1
2
3
4
5
6
7
 
$this->beConstructedWith($collection);
 
$this->totalTimeSpentOn(‘sleeping’)->shouldBe(240);
$this->totalTimeSpentOn(‘eating’)->shouldBe(60);
$this->totalTimeSpent()->shouldBe(300);

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

1
2
3
4
5
6
7
$ vendor/bin/phpspec run
Do you want me to create `TimeTracker\TimeSheet::totalTimeSpent()` for you?
 
$ vendor/bin/phpspec run
 
  25 ✘ it should calculate total time spent on task
      expected [integer:300], but got null.

Добраться до зеленого цвета легко, теперь, когда у нас есть метод sumDuration() :

1
2
3
4
public function totalTimeSpent()
{
    return $this->sumDuration($this->entries);
}

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

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

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

1
$ vendor/bin/behat -s functional

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
use TimeTracker\TimeEntry;
 
 
/**
 * @Given I have the following time entries
 */
public function iHaveTheFollowingTimeEntries(TableNode $table)
{
    $rows = $table->getHash();
 
    foreach ($rows as $row) {
        $entry = new TimeEntry;
 
        $entry->task = $row[‘task’];
        $entry->duration = $row[‘duration’];
 
        $entry->save();
    }
}

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

1
2
3
4
5
namespace TimeTracker;
 
class TimeEntry extends \Eloquent
{
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
<?php
 
return array(
 
    ‘default’ => ‘sqlite’,
 
    ‘connections’ => array(
 
        ‘sqlite’ => array(
            ‘driver’ => ‘sqlite’,
            ‘database’ => ‘:memory:’,
            ‘prefix’ => »,
        ),
 
    ),
 
);

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

1
$ php artisan migrate:make createTimeEntriesTable —create=»time_entries»
1
2
3
4
5
6
7
Schema::create(‘time_entries’, function(Blueprint $table)
{
    $table->increments(‘id’);
    $table->string(‘task’);
    $table->integer(‘duration’);
    $table->timestamps();
});

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
/**
 * @BeforeScenario
 */
public function setupDatabase()
{
    $this->app[‘artisan’]->call(‘migrate’);
}
 
/**
 * @AfterScenario
 */
public function cleanDatabase()
{
    $this->app[‘artisan’]->call(‘migrate:reset’);
}

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

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

1
2
3
4
5
6
7
8
9
/**
 * @When I generate the time sheet
 */
public function iGenerateTheTimeSheet()
{
    $this->call(‘GET’, ‘/time-sheet’);
 
    $this->crawler = new Crawler($this->client->getResponse()->getContent(), url(‘/’));
}

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

1
Route::get(‘time-sheet’, [‘as’ => ‘time_sheet’, ‘uses’ => ‘TimeEntriesController@index’]);

Чтобы добраться до зеленого, нам нужно сгенерировать контроллер:

1
2
$ php artisan controller:make TimeEntriesController
$ composer dump-autoload

И там мы идем.

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
/**
 * @Then my total time spent on :task should be :expectedDuration minutes
 */
public function myTotalTimeSpentOnTaskShouldBeMinutes($task, $expectedDuration)
{
    $actualDuration = $this->crawler->filter(‘td#’ . $task . ‘TotalDuration’)->text();
 
    PHPUnit::assertEquals($expectedDuration, $actualDuration);
}
 
/**
 * @Then my total time spent should be :expectedDuration minutes
 */
public function myTotalTimeSpentShouldBeMinutes($expectedDuration)
{
    $actualDuration = $this->crawler->filter(‘td#totalDuration’)->text();
 
    PHPUnit::assertEquals($expectedDuration, $actualDuration);
}

Искатель ищет узел <td> с идентификатором [task_name]TotalDuration или totalDuration в последнем примере.

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
use TimeTracker\TimeSheet;
use TimeTracker\TimeEntry;
 
class TimeEntriesController extends \BaseController {
 
    /**
     * Display a listing of the resource.
     *
     * @return Response
     */
    public function index()
    {
        $entries = TimeEntry::all();
        $sheet = TimeSheet::generate($entries);
 
        return View::make(‘time_entries.index’, compact(‘sheet’));
    }
 

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<h2>Time Sheet</h2>
 
<table>
    <thead>
        <th>Task</th>
        <th>Total duration</th>
    </thead>
    <tbody>
        <tr>
            <td>coding</td>
            <td id=»codingTotalDuration»>{{ $sheet->totalTimeSpentOn(‘coding’) }}</td>
        </tr>
        <tr>
            <td>documenting</td>
            <td id=»documentingTotalDuration»>{{ $sheet->totalTimeSpentOn(‘documenting’) }}</td>
        </tr>
        <tr>
            <td>meetings</td>
            <td id=»meetingsTotalDuration»>{{ $sheet->totalTimeSpentOn(‘meetings’) }}</td>
        </tr>
        <tr>
            <td><strong>Total</strong></td>
            <td id=»totalDuration»>{{ $sheet->totalTimeSpent() }}</td>
        </tr>
    </tbody>
</table>

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

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

1
2
3
4
5
6
7
namespace TimeTracker;
 
use Illuminate\Database\Eloquent\Model as Eloquent;
 
class TimeEntry extends Eloquent
{
}

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

1
$ vendor/bin/phpspec run;

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

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

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