⬆️ ⬇️

Mars rover, landing coordinates



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:





Table of contents

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 and Y , which are integers) and orientation (the string value is north , east , west or south ).

Today we will refactor LandRover :



 cd packages/navigation git checkout 2-landing 


A responsibility



Looking at the LandRover , you can find 2 reasons for the change:





This hints at two new classes extracted from LandRover : Coordinates and Orientation . In this article we will take care of the coordinates.



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' 


Conclusion



We passed the full TDD cycle: test, code, refactoring. Using phpspec was very useful for prototyping test classes, and then the code itself.



What's next



In the next article, we will highlight the Orientation from LandRover .



Previous part: Rover, Landing



')

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



All Articles