В этом руководстве мы рассмотрим два разных инструмента BDD, Behat и phpspec , и посмотрим, как они могут поддержать вас в процессе разработки. Изучение BDD может быть запутанным. Новая методология, новые инструменты и множество вопросов, таких как «что тестировать?» и «какие инструменты использовать?» Я надеюсь, что этот довольно простой пример даст вам идеи о том, как вы можете включить BDD в свой рабочий процесс.
Мое вдохновение
Я вдохновился на написание этого урока Тейлором Отвеллом, создателем фреймворка Laravel . Несколько раз я слышал, как Тейлор объясняет, почему он в основном не работает с TDD / BDD, говоря, что ему нравится сначала планировать API своего кода, прежде чем фактически приступить к его реализации. Я слышал об этом от многих разработчиков, и каждый раз я размышлял про себя: «Но это идеальный вариант использования TDD / BDD!». Тейлор говорит, что ему нравится отображать API своего кода, написав код, который он хотел бы иметь. Затем он начнет кодирование и не будет удовлетворен, пока не достигнет именно этого API. Аргумент имеет смысл, если вы только тестируете / тестируете на уровне юнитов, но, используя такой инструмент, как Behat, вы начинаете с внешнего поведения вашего программного обеспечения, которое, насколько я понимаю, в основном то, чего хочет Тейлор.
Что мы будем покрывать
В этом уроке мы создадим простой класс загрузчика конфигурационных файлов. Мы начнем с использования подхода Тейлора, а затем перейдем к подходу BDD. Примеры являются минималистичными, но все же нам придется беспокоиться о фиксаторах, статических методах и т. Д., Так что в целом, я думаю, их должно быть достаточно, чтобы показать, как Behat и phpspec могут дополнять друг друга.
Отказ от ответственности: Прежде всего, эта статья не является руководством по началу работы. Предполагает базовые знания BDD, Behat и phpspec. Вероятно, вы уже изучили эти инструменты, но все еще не можете понять, как их использовать в повседневной работе. Если вы хотите освежить в phpspec, взгляните на мое руководство по началу работы. Во-вторых, я использую Тейлора Отвелла в качестве примера. Я ничего не знаю о том, как работает Тейлор, кроме того, что я слышал, как он говорил в подкастах и т. Д. Я использую его в качестве примера, потому что он отличный разработчик (он сделал Laravel!) И потому что он хорошо известен. С таким же успехом я мог бы использовать кого-то другого, поскольку большинство разработчиков, включая меня, пока не делают BDD все время. Кроме того, я не говорю, что рабочий процесс, описанный Тейлором, плох. Я думаю, что это прекрасная идея — подумать о вашем коде, прежде чем писать его. Этот урок предназначен только для того, чтобы показать BDD способ сделать это.
Рабочий процесс Тейлора
Давайте начнем с рассмотрения того, как Тейлор может разработать проект загрузчика конфигурационных файлов. Тейлор говорит, что ему нравится просто запустить пустой текстовый файл в своем редакторе, а затем написать, как он хотел бы, чтобы разработчики могли взаимодействовать с его кодом (API). В контексте BDD это обычно называется тестированием внешнего поведения программного обеспечения и таких инструментов, как Behat, которые отлично подходят для этого. Мы увидим это в ближайшее время.
Во-первых, возможно, Тейлор примет решение о файлах конфигурации. Как они должны работать? Как и в Laravel, давайте просто использовать простые массивы PHP. Пример файла конфигурации может выглядеть так:
1
2
3
4
5
6
7
8
9
|
# config.php
<?php
return array(
‘timezone’ => ‘UTC’,
);
|
Далее, как должен работать код, который использует этот файл конфигурации? Давайте сделаем это способом Тейлора и просто напишем код, который нам нужен:
1
2
3
4
5
6
7
8
|
$config = Config::load(‘config.php’);
$config->get(‘timezone’);
$config->get(‘timezone’, ‘CET’);
$config->set(‘timezone’, ‘GMT’);
$config->get(‘timezone’);
|
Итак, это выглядит довольно хорошо. Сначала у нас есть статический вызов функции load()
, а затем три варианта использования:
- Получение «часового пояса» из файла конфигурации.
- Получение значения по умолчанию, если «часовой пояс» еще не настроен.
- Изменение параметра конфигурации путем установки его на что-то другое. Мы опишем каждый из этих вариантов использования или сценариев с Behat в ближайшее время.
Имеет смысл использовать Behat для подобных вещей. Behat не заставит нас принимать какие-либо дизайнерские решения — для этого у нас есть phpspec. Мы просто перенесем требования, которые мы только что описали, в функцию Behat, чтобы убедиться, что мы правильно поняли, когда начнем строить. Наша особенность behat послужит, так сказать, приемочным тестом для наших требований.
Если вы посмотрите на код, который мы написали, то другой причиной использования Behat вместо только phpspec является статический вызов. Нелегко тестировать статические методы, особенно если вы используете только такой инструмент, как phpspec. Мы увидим, как мы можем это сделать, когда у нас есть и Behat, и phpspec.
Настроить
Предполагая, что вы используете Composer, настройка Behat и phspec — это очень простой двухэтапный процесс.
Во-первых, вам нужен базовый файл composer.json
. Этот включает Behat и phpspec и использует psr-4 для автозагрузки классов:
01
02
03
04
05
06
07
08
09
10
11
|
{
«require-dev»: {
«behat/behat»: «~3.0»,
«phpspec/phpspec»: «~2.0»
},
«autoload»: {
«psr-4»: {
«»: «src/»
}
}
}
|
Запустите composer install
чтобы получить зависимости.
Для запуска phpspec не требуется никакой конфигурации, тогда как Behat требует от вас выполнения следующей команды для генерации базового скаффолда:
1
|
$ vendor/bin/behat —init
|
Это все, что нужно. Это не может быть твоим оправданием для того, чтобы не делать BDD!
Планирование функций с помощью Behat
Итак, теперь, когда мы все настроены, мы готовы начать делать BDD. Потому что выполнение BDD означает установку и использование Behat и phpspec, верно?
Насколько я понимаю, мы уже начали делать BDD. Мы эффективно описали внешнее поведение нашего программного обеспечения. Нашими «клиентами» в этом примере являются разработчики, которые собираются взаимодействовать с нашим кодом. Под «эффективно» я подразумеваю, что мы описали поведение так, как они его поймут. Мы могли бы взять код, который мы уже обрисовали в общих чертах, поместить его в файл README, и каждый порядочный разработчик PHP поймет его использование. Так что на самом деле это очень хорошо, но у меня есть две важные вещи, на которые стоит обратить внимание. Прежде всего, описание поведения программного обеспечения с использованием кода работает только в этом примере, потому что «клиенты» являются программистами. Обычно мы тестируем что-то, что будет использоваться «нормальными» людьми. Человеческий язык лучше, чем PHP, когда мы хотим общаться с людьми. Во-вторых, почему бы не автоматизировать это? Я не собираюсь спорить, почему это может быть хорошей идеей.
При этом, я думаю, что начать использовать Behat сейчас было бы разумным решением.
Используя Behat, мы хотим описать каждый из описанных выше сценариев. Мы не хотим широко освещать каждый крайний случай, связанный с использованием программного обеспечения. У нас есть phpspec, если это необходимо для исправления ошибок и т. Д. Я думаю, что многие разработчики, в том числе и Тейлор, считают, что им нужно все продумать и все решить, прежде чем они смогут написать тесты и спецификации. Вот почему они решили начать без BDD, потому что они не хотят решать все заранее. Это не относится к Behat, так как мы описываем внешнее поведение и использование. Чтобы использовать Behat для описания функции, нам не нужно решать что-то большее, чем в примере выше, с использованием необработанного текстового файла. Нам просто нужно определить требования функции — в этом случае внешний API класса загрузчика файла конфигурации.
Теперь давайте возьмем приведенный выше код PHP и превратим его в функцию Behat, используя английский язык (фактически используя язык Gherkin).
Создайте файл в каталоге features/
именем config.feature
и заполните следующие сценарии:
Особенность: файлы конфигурации Для того, чтобы настроить мое приложение Как разработчик Мне нужно иметь возможность хранить параметры конфигурации в файле Сценарий: получение настроенного параметра Учитывая, что есть файл конфигурации И опция «часовой пояс» настроен на «UTC» Когда я загружаю файл конфигурации Тогда я должен получить «UTC» в качестве опции «часовой пояс» Сценарий: получение ненастроенного параметра со значением по умолчанию Учитывая, что есть файл конфигурации И опция «часовой пояс» еще не настроена Когда я загружаю файл конфигурации Тогда я должен получить значение по умолчанию «CET» в качестве опции «часовой пояс» Сценарий: настройка параметра конфигурации Учитывая, что есть файл конфигурации И опция «часовой пояс» настроен на «UTC» Когда я загружаю файл конфигурации И я установил опцию конфигурации «часовой пояс» на «GMT» Тогда я должен получить «GMT» в качестве опции «часовой пояс»
В этой функции мы описываем, как «разработчик» может хранить параметры конфигурации. Мы не заботимся о внутреннем поведении — мы будем, когда мы начнем использовать phpspec. Пока эта функция работает зеленым, нам все равно, что происходит за кулисами.
Давайте запустим Behat и посмотрим, что он думает о нашей функции:
1
|
$ vendor/bin/behat —dry-run —append-snippets
|
С помощью этой команды мы говорим Behat добавить необходимые определения шагов в наш контекст функции. Я не буду вдаваться в подробности о Behat, но это добавляет кучу пустых методов в наш класс FeatureContext
который сопоставляется с нашими шагами функций выше.
В качестве примера рассмотрим определение шага, добавленное Behat для шага there is a configuration file
который мы используем как шаг «Данные» во всех трех сценариях:
1
2
3
4
5
6
7
|
/**
* @Given there is a configuration file
*/
public function thereIsAConfigurationFile()
{
throw new PendingException();
}
|
Теперь все, что нам нужно сделать, это заполнить некоторый код, чтобы описать это.
Написание определений шагов
Прежде чем мы начнем, у меня есть два важных замечания по поводу определений шагов:
- Суть в том, чтобы доказать, что поведение сейчас не такое, как мы хотим. После этого мы можем начать разрабатывать наш код, чаще всего используя phpspec, чтобы перейти к зеленому цвету.
- Реализация определений шагов не важна — нам просто нужно что-то, что работает и достигает «1». Мы можем рефакторинг позже.
Если вы запустите vendor/bin/behat
, вы увидите, что все сценарии теперь имеют ожидающие действия.
Начнем с первого шага. Given there is a configuration file
. Мы будем использовать фиксатор файла конфигурации, чтобы позже мы могли использовать метод static load()
. Мы заботимся о методе static load()
потому что он дает нам хороший API Config::load()
, очень похожий на фасады Laravel. Этот шаг может быть реализован многими способами. На данный момент, я думаю, мы должны просто убедиться, что у нас есть доступное приспособление и что оно содержит массив:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
/**
* @Given there is a configuration file
*/
public function thereIsAConfigurationFile()
{
if ( ! file_exists(‘fixtures/config.php’))
throw new Exception(«File ‘fixtures/config.php’ not found»);
$config = include ‘fixtures/config.php’;
if ( ! is_array($config))
throw new Exception(«File ‘fixtures/config.php’ should contain an array»);
}
|
На этом шаге мы доберемся до зеленого, не реализовав никакого кода, кроме как сделать исправление. Цель Given
шага — привести систему в известное состояние. В этом случае это означает, что у нас есть файл конфигурации.
Чтобы перейти к нашему первому зеленому шагу, нам просто нужно создать прибор:
1
2
3
4
|
# fixtures/config.php
<?php
return array();
|
Далее у нас есть шаг And
, который в данном случае является просто псевдонимом для Given
. Мы хотим убедиться, что файл конфигурации содержит опцию для часового пояса. Опять же, это связано только с нашим приспособлением, поэтому нас это не сильно волнует. Я выполнил следующий (хакерский) код вместе, чтобы выполнить это:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
/**
* @Given the option :option is configured to :value
*/
public function theOptionIsConfiguredTo($option, $value)
{
$config = include ‘fixtures/config.php’;
if ( ! is_array($config)) $config = [];
$config[$option] = $value;
$content = «<?php\n\nreturn » .
file_put_contents(‘fixtures/config.php’, $content);
}
|
Приведенный выше код не очень хорош, но он выполняет то, что ему нужно. Это позволяет нам манипулировать нашим прибором изнутри нашей функции. Если вы запустите Behat, вы увидите, что он добавил опцию «timezone» к устройству config.php
:
1
2
3
4
5
|
<?php
return array (
‘timezone’ => ‘UTC’,
);
|
Сейчас настало время ввести некоторые из оригинального «кода Тейлора»! Шаг, When I load the configuration file
будет состоять из кода, который нам действительно нужен. Мы введем часть кода из необработанного текстового файла из предыдущего и убедимся, что он работает:
1
2
3
4
5
6
7
|
/**
* @When I load the configuration file
*/
public function iLoadTheConfigurationFile()
{
$this->config = Config::load(‘fixtures/config.php’);
}
|
Запуск Behat, конечно, не удастся, так как Config
еще не существует. Позвольте нам принести phpspec на помощь!
Проектирование с Phpspec
Когда мы запустим Behat, мы получим фатальную ошибку:
1
|
PHP Fatal error: Class ‘Config’ not found…
|
К счастью, у нас есть phpspec, включая его потрясающие генераторы кода:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
$ vendor/bin/phpspec desc «Config»
Specification for Config created in …/spec/ConfigSpec.php.
$ vendor/bin/phpspec run —format=pretty
Do you want me to create `Config` for you?
$ vendor/bin/phpspec run —format=pretty
Config
10 ✔ is initializable
1 specs
1 examples (1 passed)
7ms
|
С помощью этих команд phpspec создал для нас следующие два файла:
1
2
3
4
|
spec/
`— ConfigSpec.php
src/
`— Config.php
|
Это избавило нас от первой фатальной ошибки, но Behat все еще не работает:
1
|
PHP Fatal error: Call to undefined method Config::load() in …
|
load()
будет статическим методом, поэтому его сложно определить с помощью phpspec. По двум причинам это нормально, хотя:
- Поведение метода
load()
будет очень простым. Если позже нам понадобится больше сложности, мы можем извлечь логику для небольших тестируемых методов. - Поведение, как пока, достаточно хорошо охвачено Behat. Если метод не загружает файл в массив должным образом, Behat будет работать на нас.
Это одна из тех ситуаций, когда многие разработчики попадают в стену. Они выбросят phpspec и решат, что он отстой и работает против них. Но посмотрите, как хорошо Behat и phpspec дополняют друг друга здесь?
Вместо того, чтобы пытаться получить 100% охват phpspec, давайте просто реализуем простую функцию load()
и будем уверены, что она покрыта Behat:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
<?php
class Config
{
protected $settings;
public function __construct()
{
$this->settings = array();
}
public static function load($path)
{
$config = new static();
if (file_exists($path))
$config->settings = include $path;
return $config;
}
}
|
Мы уверены, что наши параметры конфигурации теперь загружены. Если нет, остальные наши шаги потерпят неудачу, и мы можем рассмотреть это снова.
Создание элемента с итерацией
Вернемся к зеленому с Behat и phpspec, теперь мы можем посмотреть на наш следующий функциональный шаг. Then I should get 'UTC' as 'timezone' option
.
01
02
03
04
05
06
07
08
09
10
|
/**
* @Then I should get :value as :option option
*/
public function iShouldGetAsOption($value, $option)
{
$actual = $this->config->get($option);
if ( ! strcmp($value, $actual) == 0)
throw new Exception(«Expected {$actual} to be ‘{$option}’.»);
}
|
На этом этапе мы пишем больше того кода, который нам хотелось бы иметь. Запустив Behat, мы увидим, что у нас нет метода get()
:
1
|
PHP Fatal error: Call to undefined method Config::get() in …
|
Настало время вернуться к phpspec и разобраться с этим.
Тестирование аксессоров и мутаторов, добытчиков и сеттеров АКА, почти похоже на эту старую дилемму с курицей или яйцом. Как мы можем протестировать метод get()
если у нас еще нет метода set()
и наоборот. Как я обычно это делаю, так это проверяю их обоих одновременно. Это означает, что мы на самом деле собираемся реализовать функциональность для настройки параметров конфигурации, хотя мы еще не достигли этого сценария.
Следующий пример должен сделать:
1
2
3
4
5
6
7
8
|
function it_gets_and_sets_a_configuration_option()
{
$this->get(‘foo’)->shouldReturn(null);
$this->set(‘foo’, ‘bar’);
$this->get(‘foo’)->shouldReturn(‘bar’);
}
|
Во-первых, генераторы phpspec помогут нам начать:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
$ vendor/bin/phpspec run —format=pretty
Do you want me to create `Config::get()` for you?
$ vendor/bin/phpspec run —format=pretty
Do you want me to create `Config::set()` for you?
$ vendor/bin/phpspec run —format=pretty
Config
10 ✔ is initializable
15 ✘ gets and sets a configuration option
expected «bar», but got null.
…
1 specs
2 examples (1 passed, 1 failed)
9ms
|
Теперь вернемся к зеленому:
01
02
03
04
05
06
07
08
09
10
11
12
|
public function get($option)
{
if ( ! isset($this->settings[$option]))
return null;
return $this->settings[$option];
}
public function set($option, $value)
{
$this->settings[$option] = $value;
}
|
И там мы идем:
01
02
03
04
05
06
07
08
09
10
11
|
$ vendor/bin/phpspec run —format=pretty
Config
10 ✔ is initializable
15 ✔ gets and sets a configuration option
1 specs
2 examples (2 passed)
9ms
|
Это проделало нам долгий путь. Запустив Behat, мы видим, что сейчас мы находимся во втором сценарии. Далее нам нужно реализовать опцию по умолчанию, так как get()
просто возвращает null
прямо сейчас.
Первый функциональный шаг похож на тот, который мы написали ранее. Вместо того, чтобы добавить опцию в массив, мы unset
ее:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
/**
* @Given the option :option is not yet configured
*/
public function theOptionIsNotYetConfigured($option)
{
$config = include ‘fixtures/config.php’;
if ( ! is_array($config)) $config = [];
unset($config[$option]);
$content = «<?php\n\nreturn » .
file_put_contents(‘fixtures/config.php’, $content);
}
|
Это не красиво. Я знаю! Мы могли бы, конечно, рефакторинг, так как мы повторяем себя, но это не является целью этого урока.
Второй функциональный шаг также выглядит знакомым и состоит в основном из копирования и вставки из более ранних версий:
01
02
03
04
05
06
07
08
09
10
|
/**
* @Then I should get default value :default as :option option
*/
public function iShouldGetDefaultValueAsOption($default, $option)
{
$actual = $this->config->get($option, $default);
if ( ! strcmp($default, $actual) == 0)
throw new Exception(«Expected {$actual} to be ‘{$default}’.»);
}
|
get()
возвращает null
. Давайте перейдем к phpspec и напишем пример, чтобы решить это:
1
2
3
4
5
6
7
8
|
function it_gets_a_default_value_when_option_is_not_set()
{
$this->get(‘foo’, ‘bar’)->shouldReturn(‘bar’);
$this->set(‘foo’, ‘baz’);
$this->get(‘foo’, ‘bar’)->shouldReturn(‘baz’);
}
|
Сначала мы проверяем, что получаем значение по умолчанию, если «опция» еще не настроена. Во-вторых, мы гарантируем, что опция по умолчанию не перезаписывает настроенную опцию.
На первый взгляд, в этом случае phpspec может показаться излишним, так как мы уже почти тестируем то же самое с Behat. Мне нравится использовать phpspec для спецификации крайних случаев, которые как бы подразумеваются в сценарии. А также, генераторы кода phpspec действительно хороши. Я использую их для всего, и я работаю быстрее, когда использую phpspec.
Теперь phpspec подтверждает то, что Behat уже сказал нам:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
$ vendor/bin/phpspec run —format=pretty
Config
10 ✔ is initializable
15 ✔ gets and sets a configuration option
24 ✘ gets a default value when option is not set
expected «bar», but got null.
…
1 specs
3 examples (2 passed, 1 failed)
9ms
|
Чтобы вернуться к зеленому цвету, мы добавим «ранний возврат» в метод get()
:
01
02
03
04
05
06
07
08
09
10
|
public function get($option, $defaultValue = null)
{
if ( ! isset($this->settings[$option]) and ! is_null($defaultValue))
return $defaultValue;
if ( ! isset($this->settings[$option]))
return null;
return $this->settings[$option];
}
|
Мы видим, что phpspec теперь счастлив:
01
02
03
04
05
06
07
08
09
10
11
12
|
$ vendor/bin/phpspec run —format=pretty
Config
10 ✔ is initializable
15 ✔ gets and sets a configuration option
24 ✔ gets a default value when option is not set
1 specs
3 examples (3 passed)
9ms
|
И Бехат, и мы тоже.
Мы закончили со вторым сценарием, и у нас осталось еще одно. Для последнего сценария нам нужно только написать определение шага для параметра And I set the 'timezone' configuration option to 'GMT'
шага And I set the 'timezone' configuration option to 'GMT'
:
1
2
3
4
5
6
7
|
/**
* @When I set the :option configuration option to :value
*/
public function iSetTheConfigurationOptionTo($option, $value)
{
$this->config->set($option, $value);
}
|
Поскольку мы уже реализовали метод set()
, этот шаг уже зеленый:
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
|
$ vendor/bin/behat
Feature: Configuration files
In order to configure my application
As a developer
I need to be able to store configuration options in a file
Scenario: Getting a configured option # features/config.feature:6
Given there is a configuration file # FeatureContext::thereIsAConfigurationFile()
And the option ‘timezone’ is configured to ‘UTC’ # FeatureContext::theOptionIsConfiguredTo()
When I load the configuration file # FeatureContext::iLoadTheConfigurationFile()
Then I should get ‘UTC’ as ‘timezone’ option # FeatureContext::iShouldGetAsOption()
Scenario: Getting a non-configured option with a default value # features/config.feature:12
Given there is a configuration file # FeatureContext::thereIsAConfigurationFile()
And the option ‘timezone’ is not yet configured # FeatureContext::theOptionIsNotYetConfigured()
When I load the configuration file # FeatureContext::iLoadTheConfigurationFile()
Then I should get default value ‘CET’ as ‘timezone’ option # FeatureContext::iShouldGetDefaultValueAsOption()
Scenario: Setting a configuration option # features/config.feature:18
Given there is a configuration file # FeatureContext::thereIsAConfigurationFile()
And the option ‘timezone’ is configured to ‘UTC’ # FeatureContext::theOptionIsConfiguredTo()
When I load the configuration file # FeatureContext::iLoadTheConfigurationFile()
And I set the ‘timezone’ configuration option to ‘GMT’ # FeatureContext::iSetTheConfigurationOptionTo()
Then I should get ‘GMT’ as ‘timezone’ option # FeatureContext::iShouldGetAsOption()
3 scenarios (3 passed)
13 steps (13 passed)
0m0.04s (8.92Mb)
|
Заворачивать
Все красиво и зелено, поэтому давайте подведем итоги и посмотрим, чего мы достигли.
Мы эффективно описали внешнее поведение загрузчика конфигурационных файлов, сначала с помощью подхода Тейлора, а затем с помощью традиционного подхода BDD. Далее мы реализовали эту функцию, используя phpspec для разработки и описания внутреннего поведения. Пример, над которым мы работали, довольно прост, но мы рассмотрели основы. Если нам нужно больше сложности, мы можем просто расширить то, что у нас уже есть. Используя BDD, у нас есть как минимум три варианта:
- Если мы наблюдаем ошибку или нуждаемся в изменении некоторых внутренних компонентов нашего программного обеспечения, мы можем описать это с помощью phpspec. Напишите неудачный пример, демонстрирующий ошибку, и напишите код, необходимый для перехода к зеленому цвету.
- Если нам нужно добавить новый вариант использования к тому, что у нас есть, мы можем добавить сценарий в
config.feature
. Затем мы можем итеративно прорабатывать каждый шаг, используя Behat и phpspec. - Если нам нужно реализовать новую функцию, такую как поддержка файлов конфигурации YAML, мы можем написать совершенно новую функцию и начать все сначала, используя подход, который мы использовали в этом руководстве.
С этой базовой установкой у нас нет оправданий, чтобы не написать провальный тест или спецификацию, прежде чем мы напишем наш код. То, что мы создали, теперь покрыто тестами, которые значительно облегчат работу с ним в будущем. Добавьте к этому, что наш код также полностью документирован. Предполагаемые варианты использования описаны на простом английском языке, а внутренняя работа описана в наших спецификациях. Эти две вещи позволят другим разработчикам легко понять и работать с базой кода.
Я надеюсь, что это руководство помогло вам лучше понять, как BDD можно использовать в контексте PHP с Behat и phpspec. Если у вас есть какие-либо вопросы или комментарии, пожалуйста, оставьте их ниже в разделе комментариев.
Спасибо, что читаете вместе!