Марсохід, Посадка


У цій серії статей ми будуємо програмне забезпечення марсохода в відповідно з наступними специфікаціями. Це дозволить застосувати на практиці такі підходи:
  • Monolithic Repositories — MonoRepo (Монолітні репозиторії)
  • Command/Query Responsibility Segregation CQRS (Сегрегація відповідальності на читання і запис)
  • Event Sourcing — ES (Події як джерело)
  • Test Driven Development — TDD (Розробка через тестування)
ЗмістМарсохід, Введення
Марсохід, Ініціалізація
Марсохід, Посадка
Раніше ми створили пакет навігації, тепер можна приступати до розробки першого варіанту використання:
Марсохід повинен буде спочатку приземлитися в заданому положенні. Положення складається з координат (
X
та
Y
, що є цілими числами) і орієнтації (значення рядка
north
,
east
,
west
або
south
).
Спрощуємо Command Bus (Командна Шина)
Патерн Command Bus складається з 3х класів:
  • клас
    Command
    , який перевіряє вхідний набір даних з іменем якого будуть застосовуватися необхідні маніпуляції (наприклад,
    LandRover
    )
  • пов'язаний з ним (ставлення один до одного)
    CommandHandler
    , що реалізує логіку для конкретного випадку використання
  • CommandBus
    , який приймає команди і виконує відповідні
    CommandHandler
    також підтримує роботу через
    Middleware
Ми збираємося спростити цей архітектурний шаблон для нашого марсохода, опускаючи клас
CommandBus
, тому що нам насправді не потрібно реалізовувати
middleware
або шукати відповідний
CommandHandler
для отриманої
Command
.
Почнемо з створення класу
Command
, який подбає про перевірку вхідних параметрів:
cd packages/navigation
git checkout -b 2-landing

Приземлення
Ми збираємося ініціалізувати тестовий клас
LandRover
, використовуючи phpspec:
vendor/bin/phpspec describe 'MarsRover\Navigation\LandRover'

Виходить згенерований клас
spec/MarsRover/Navigation/LandRoverSpec.php
:
namespace spec\MarsRover\Navigation;

use MarsRover\Navigation\LandRover;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;

class LandRoverSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType(LandRover::class);
}
}

Нам залишається відредагувати його, почнемо з опису вхідних параметрів:
namespace spec\MarsRover\Navigation;

use PhpSpec\ObjectBehavior;

class LandRoverSpec extends ObjectBehavior
{
const X = 23;
const Y = 42;
const ORIENTATION = 'north';

function it_has_x_coordinate()
{
$this->beConstructedWith(
self::X,
self::Y,
self::ORIENTATION
);

$this->getX()->shouldBe(self::X);
}

function it_has_y_coordinate()
{
$this->beConstructedWith(
self::X,
self::Y,
self::ORIENTATION
);

$this->getY()->shouldBe(self::Y);
}

function it_has_an_orientation()
{
$this->beConstructedWith(
self::X,
self::Y,
self::ORIENTATION
);

$this->getOrientation()->shouldBe(self::ORIENTATION);
}
}

Тепер можна запустити тести:
vendor/bin/phpspec run

Це згенерує нам
src/MarsRover/Navigation/LandRover.php
файл:
namespace MarsRover\Navigation;

class LandRover
{
private $argument1;

private $argument2;

private $argument3;

public function __construct($argument1, $argument2, $argument3)
{
$this->argument1 = $argument1;
$this->argument2 = $argument2;
$this->argument3 = $argument3;
}

public function getX()
{
}

public function getY()
{
}

public function getOrientation()
{
}
}

Все, що нам потрібно зробити, це змінити його:
namespace MarsRover\Navigation;

class LandRover
{
private $x;
private $y;
private $orientation;

public function __construct($x, $y, $orientation)
{
$this->x = $x;
$this->y = $y;
$this->orientation = $orientation;
}

public function getX() : int
{
return $this->x;
}

public function getY() : int
{
return $this->y;
}

public function getOrientation() : string
{
return $this->orientation;
}
}

Давайте знову виконаємо тести:
vendor/bin/phpspec run

Всі зелені! Але наша робота ще не закінчена, ми не описали неприпустимі вхідні параметри:
namespace spec\MarsRover\Navigation;

use PhpSpec\ObjectBehavior;

class LandRoverSpec extends ObjectBehavior
{
const X = 23;
const Y = 42;
const ORIENTATION = 'north';

function it_has_x_coordinate()
{
$this->beConstructedWith(
self::X,
self::Y,
self::ORIENTATION
);

$this->getX()->shouldBe(self::X);
}

function it_cannot_have_non_integer_x_coordinate()
{
$this->beConstructedWith(
'Nobody expects the Spanish Inquisition!',
self::Y,
self::ORIENTATION
);

$this->shouldThrow(
\InvalidArgumentException::class
)->duringInstantiation();
}

function it_has_y_coordinate()
{
$this->beConstructedWith(
self::X,
self::Y,
self::ORIENTATION
);

$this->getY()->shouldBe(self::Y);
}

function it_cannot_have_non_integer_y_coordinate()
{
$this->beConstructedWith(
self::X,
'No one expects the Spanish Inquisition!',
self::ORIENTATION
);

$this->shouldThrow(
\InvalidArgumentException::class
)->duringInstantiation();
}

function it_has_an_orientation()
{
$this->beConstructedWith(
self::X,
self::Y,
self::ORIENTATION
);

$this->getOrientation()->shouldBe(self::ORIENTATION);
}

function it_cannot_have_a_non_cardinal_orientation()
{
$this->beConstructedWith(
self::X,
self::Y,
'A hareng!'
);

$this->shouldThrow(
\InvalidArgumentException::class
)->duringInstantiation();
}
}

Знову перевіряємо:
vendor/bin/phpspec run

Вони падають, тому що ми повинні перевіряти вхідні параметри:
namespace MarsRover\Navigation;

class LandRover
{
const VALID_ORIENTATIONS = ['north', 'east', 'west', 'south'];

private $x;
private $y;
private $orientation;

public function __construct($x, $y, $orientation)
{
if (false === is_int($x)) {
throw new \InvalidArgumentException(
'X coordinate must be an integer'
);
}
$this->x = $x;
if (false === is_int($y)) {
throw new \InvalidArgumentException(
'Y coordinate must be an integer'
);
}
$this->y = $y;
if (false === in_array($orientation, self::VALID_ORIENTATIONS, true)) {
throw new \InvalidArgumentException(
'Orientation must be one of:'
.implode(', ', self::VALID_ORIENTATIONS)
);
}
$this->orientation = $orientation;
}

public function getX() : int
{
return $this->x;
}

public function getY() : int
{
return $this->y;
}

public function getOrientation() : string
{
return $this->orientation;
}
}

І знову виконаємо тести:
vendor/bin/phpspec run

Всі пройшли! Тепер ми можемо закоммитить нашу роботу:
git add -A
git commit -m '2: Created LandRover'

Висновок
Ми зробили перші кроки в TDD: написали тести, потім-код, і за допомогою
phpspec
цей процес спростився.
Оскільки ми пишемо ці тести в описовому вигляді (тестові методи іменуються у вигляді пропозицій), то ми можемо використовувати їх в якості виконуваною специфікації для самоконтролю!
phpspec
дозволяє відображати їх в явному вигляді:
vendor/bin/phpspec run --format=pretty

Має відображатися:
MarsRover\Navigation\LandRover

13 has x coordinate
24 cannot have non integer x coordinate
37 has y coordinate
48 cannot have non integer y coordinate
61 has an orientation
72 cannot have a non cardinal orientation

1 specs
6 examples (6 passed)
10ms

Примітка:
navigation
-тести можна запускати з MonoRepo:
cd ../../
composer update --optimize-autoloader
vendor/bin/phpspec run

далі
У наступній статті ми завершимо цикл TDD рефакторінгом
LandRover
: винесемо
x
та
y
координати в їх власні класи.
Попередня частина: Марсохід, Ініціалізація
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.