In this series of articles, we build the rover software in accordance with the following specifications . This will allow us to practice the following approaches:
Mars Rover, Introduction
Mars rover Initialization
Rover, Landing
Mars rover, landing coordinates
In the previous parts, we created a navigation package , and in it the LandRover
class, which validates the input parameters for our first use:
The rover will first have to land in a predetermined position. The position consists of the coordinates (X
andY
, which are integers) and orientation (the string value isnorth
,east
,west
orsouth
).
Today we will refactor LandRover
:
cd packages/navigation git checkout 2-landing
Looking at the LandRover
, you can find 2 reasons for the change:
x
and y
coordinates can take float
values, or have an additional z
axisThis hints at two new classes extracted from LandRover
: Coordinates
and Orientation
. In this article we will take care of the coordinates.
First make a test class using phpspec :
vendor/bin/phpspec describe 'MarsRover\Navigation\Coordinates'
A new spec/MarsRover/Navigation/CoordinatesSpec.php
file will appear:
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); } }
We will edit it, using the groundwork from the test class for 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(); } }
If you run the tests now, the CoordinatesSpec
class will be loaded:
vendor/bin/phpspec run
And he will create us the file 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() { } }
Now it remains only to complete what we have already done for the LandRover
class:
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; } }
Run the tests:
vendor/bin/phpspec run
All green! Let's update the LandRover
test class to use a new class of coordinates in it:
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(); } }
You no longer need to validate the values of x
and y
, we will entrust all this to the Coordinates
class, it will take care of this for us. Now you can update the LandRover
class:
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; } }
Check again everything is in order, running the tests:
vendor/bin/phpspec run
Great, it's all over! Let's change the changes:
git add -A git commit -m '2: Created Coordinates'
We passed the full TDD cycle: test, code, refactoring. Using phpspec
was very useful for prototyping test classes, and then the code itself.
In the next article, we will highlight the Orientation
from LandRover
.
Previous part: Rover, Landing
Source: https://habr.com/ru/post/315684/