📜 ⬆️ ⬇️

Rover, Landing


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:




Earlier we created a navigation package , now we can start developing the first use case:


The rover will first have to land in a predetermined position. The position consists of the coordinates ( X and Y , which are integers) and orientation (the string value is north , east , west or south ).

Simplify the Command Bus


Pattern Command Bus consists of 3 classes:



We are going to simplify this architectural template for our rover, omitting the CommandBus class, since we do not really need to implement middleware or search for the corresponding CommandHandler for the resulting Command .


Let's start by creating the Command class, which will take care of checking the input parameters:


 cd packages/navigation git checkout -b 2-landing 

Landing


We are going to initialize the test class for LandRover using phpspec :


 vendor/bin/phpspec describe 'MarsRover\Navigation\LandRover' 

It turns out the generated class 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); } } 

It remains for us to edit it, starting with a description of the input parameters:


 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); } } 

Now you can run the tests:


 vendor/bin/phpspec run 

This will generate us the src/MarsRover/Navigation/LandRover.php file:


 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() { } } 

All we have to do is change it:


 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; } } 

Let's run the tests again:


 vendor/bin/phpspec run 

All green! But our work is not finished yet, we have not described invalid input parameters:


 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(); } } 

Check again:


 vendor/bin/phpspec run 

They fall because we have to check the input parameters:


 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; } } 

And again run the tests:


 vendor/bin/phpspec run 

All gone! Now we can commit our work:


 git add -A git commit -m '2: Created LandRover' 

Conclusion


We took the first steps in TDD : we wrote the tests, then the code, and with the help of phpspec this process was simplified.


Since we write these tests in a descriptive form (test methods are referred to as sentences), we can use them as an executable specification for self-control! phpspec allows phpspec to display them explicitly:


 vendor/bin/phpspec run --format=pretty 

Should be displayed:


 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 

Note : navigation tests can be run from MonoRepo:
 cd ../../ composer update --optimize-autoloader vendor/bin/phpspec run 


What's next


In the next article, we complete the LandRover TDD cycle refactoring: extract the x and y coordinates into their own classes.


Previous part: Rover, Initialization
Next part: Mars rover, landing coordinates


')

Source: https://habr.com/ru/post/314958/


All Articles