В этой серии статей мы строим программное обеспечение марсохода в соответствии со следующими спецификациями. Это позволит применить нам на практике следующие подходы:
- Monolithic Repositories — MonoRepo (Монолитные репозитории)
- Command/Query Responsibility Segregation — CQRS (Сегрегация ответственности на чтение и запись)
- Event Sourcing — ES (События как источник)
- Test Driven Development — TDD (Разработка через тестирование)
Марсоход, Введение
Марсоход, Инициализация
Марсоход, Посадка
Марсоход, Координаты посадки
В предыдущих частях мы создали пакет навигации, а в нем LandRover класс, который валидирует входные параметры для нашего первого способа использования:
Марсоход должен будет сначала приземлиться в заданном положении. Положение состоит из координат (
XиY, являющихся целыми числами) и ориентации (строковое значениеnorth,east,westилиsouth).
Сегодня мы будем рефакторить LandRover:
cd packages/navigation git checkout 2-landing
Обязанности
Посмотрев на LandRover, можно найти 2 причины для изменения:
- координаты
xиyмогут приниматьfloatзначения, или иметь дополнительную осьz - ориентация может быть в угловых градусах или иметь вертикальную ориентацию.
Это намекает на два новых класса, извлеченных из LandRover: Coordinates и Orientation. В этой статье мы позаботимся о координатах.
Координаты
Сначала сделаем тестовый класс, используя phpspec:
vendor/bin/phpspec describe 'MarsRover\Navigation\Coordinates'
Появится новый файл spec/MarsRover/Navigation/CoordinatesSpec.php:
namespace spec\MarsRover\Navigation; use MarsRover\Navigation\Coordinates; use PhpSpec\ObjectBehavior; use Prophecy\Argument; class CoordinatesSpec extends ObjectBehavior { function it_is_initializable() { $this->shouldHaveType(Coordinates::class); } }
Мы отредактируем его, используя наработки из тестового класса для LandRover:
namespace spec\MarsRover\Navigation; use PhpSpec\ObjectBehavior; class CoordinatesSpec extends ObjectBehavior { const X = 23; const Y = 42; function it_has_x_coordinate() { $this->beConstructedWith( self::X, self::Y ); $this->getX()->shouldBe(self::X); } function it_cannot_have_non_integer_x_coordinate() { $this->beConstructedWith( 'Nobody expects the Spanish Inquisition!', self::Y ); $this->shouldThrow( \InvalidArgumentException::class )->duringInstantiation(); } function it_has_y_coordinate() { $this->beConstructedWith( self::X, self::Y ); $this->getY()->shouldBe(self::Y); } function it_cannot_have_non_integer_y_coordinate() { $this->beConstructedWith( self::X, 'No one expects the Spanish Inquisition!' ); $this->shouldThrow( \InvalidArgumentException::class )->duringInstantiation(); } }
Если запустить тесты сейчас, будет загружен класс CoordinatesSpec:
vendor/bin/phpspec run
И он создаст нам файл src/MarsRover/Navigation/Coordinates.php:
namespace MarsRover\Navigation; class Coordinates { private $argument1; private $argument2; public function __construct($argument1, $argument2) { $this->argument1 = $argument1; $this->argument2 = $argument2; } public function getX() { } public function getY() { } }
Теперь остается только завершить то, что мы уже делали для класса LandRover:
namespace MarsRover\Navigation; class Coordinates { private $x; private $y; public function __construct($x, $y) { 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; } public function getX() : int { return $this->x; } public function getY() : int { return $this->y; } }
Запустим тесты:
vendor/bin/phpspec run
Все зеленые! Обновим тестовый класс LandRover для использования в нем нового класса координат:
namespace spec\MarsRover\Navigation; use PhpSpec\ObjectBehavior; class LandRoverSpec extends ObjectBehavior { const X = 23; const Y = 42; const ORIENTATION = 'north'; function it_has_coordinates() { $this->beConstructedWith( self::X, self::Y, self::ORIENTATION ); $coordinates = $this->getCoordinates(); $coordinates->getX()->shouldBe(self::X); $coordinates->getY()->shouldBe(self::Y); } 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(); } }
Больше не нужно валидировать значения x и y, все это доверим классу Coordinates, он позаботится об этом для нас. Теперь можно обновить класс LandRover:
namespace MarsRover\Navigation; class LandRover { const VALID_ORIENTATIONS = ['north', 'east', 'west', 'south']; private $coordinates; private $orientation; public function __construct($x, $y, $orientation) { $this->coordinates = new Coordinates($x, $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 getCoordinates() : Coordinates { return $this->coordinates; } public function getOrientation() : string { return $this->orientation; } }
Еще раз проверим все ли в порядке, запустив тесты:
vendor/bin/phpspec run
Отлично, все прошло! Закоммитим изменения:
git add -A git commit -m '2: Created Coordinates'
Заключение
Мы прошли полный цикл TDD: тест, код, рефакторинг. Использование phpspec было очень полезно для прототипирования тестовых классов, а затем и самого кода.
Что дальше
В следующей статье мы выделим Orientation из LandRover.
Предыдущая часть: Марсоход, Посадка
ссылка на оригинал статьи https://habrahabr.ru/post/315684/

Добавить комментарий