Mars Rover, Driving 10/08/2016
In this series we're building the software of a Mars Rover, according to the following specifications. It allows us to practice the followings:
- Monolithic Repositories (MonoRepo)
- Command / Query Responsibility Segregation (CQRS)
- Event Sourcing (ES)
- Test Driven Development (TDD)
We just finished developing the first use case, so we can now get cracking on the second one, Driving the rover:
Once a rover has been landed on Mars it is possible to drive them, using instructions such as
move_forward
(keeps orientation, but moves along thex
ory
axis) orturn_left
/turn_right
(keeps the same coordinates, but changes the orientation).
Drive Rover
Again, we start by creating a class with the name of our use case. It will take care of doing a simple validation on the input provided by the user:
cd packages/navigation
git checkout -b 4-driving
Using phpspec, we bootstrap the test class:
vendor/bin/phpspec describe 'MarsRover\Navigation\DriveRover'
This should generate this spec/MarsRover/Navigation/DriveRoverSpec.php
class:
<?php
namespace spec\MarsRover\Navigation;
use MarsRover\Navigation\DriveRover;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class DriveRoverSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType(DriveRover::class);
}
}
This leaves us the task of editing it to start describing input parameters:
<?php
namespace spec\MarsRover\Navigation;
use PhpSpec\ObjectBehavior;
class DriveRoverSpec extends ObjectBehavior
{
const DRIVING_INSTRUCTION = 'move_forward';
function it_has_a_driving_instruction()
{
$this->beConstructedWith(
self::DRIVING_INSTRUCTION
);
$this->getInstruction()->shouldBe(self::DRIVING_INSTRUCTION);
}
}
We can now run the tests:
vendor/bin/phpspec run
This will generate the src/MarsRover/Navigation/DriveRover.php
file:
<?php
namespace MarsRover\Navigation;
class DriveRover
{
private $argument;
public function __construct($argument)
{
$this->argument = $argument;
}
public function getInstruction()
{
}
}
All we need to do is to edit it:
<?php
namespace MarsRover\Navigation;
class DriveRover
{
private $instruction;
public function __construct($instruction)
{
$this->instruction = $instruction;
}
public function getInstruction() : string
{
return $this->instruction;
}
}
Let's check the tests:
vendor/bin/phpspec run
All green! Now let's add some unhappy scenarios to our tests:
<?php
namespace spec\MarsRover\Navigation;
use PhpSpec\ObjectBehavior;
class DriveRoverSpec extends ObjectBehavior
{
const DRIVING_INSTRUCTION = 'move_forward';
const INVALID_DRIVING_INSTRUCTION = 'wake_up_polly_parrot';
function it_has_a_driving_instruction()
{
$this->beConstructedWith(
self::DRIVING_INSTRUCTION
);
$this->getInstruction()->shouldBe(self::DRIVING_INSTRUCTION);
}
function it_cannot_have_invalid_instruction()
{
$this->beConstructedWith(
self::INVALID_DRIVING_INSTRUCTION
);
$this->shouldThrow(
\InvalidArgumentException::class
)->duringInstantiation();
}
}
We can run the tests:
vendor/bin/phpspec run
They fail! So let's complete the code:
<?php
namespace MarsRover\Navigation;
class DriveRover
{
const VALID_INSTRUCTIONS = [
'move_forward',
'turn_left',
'turn_right',
];
private $instruction;
public function __construct($instruction)
{
if (false === in_array($instruction, self::VALID_INSTRUCTIONS, true)) {
throw new \InvalidArgumentException(
'Instruction should be one of: '
.implode(', ', self::VALID_INSTRUCTIONS)
);
}
$this->instruction = $instruction;
}
public function getInstruction() : string
{
return $this->instruction;
}
}
And re-run the tests:
vendor/bin/phpspec run
All green! We can now commit our work:
git add -A
git commit -m '4: Created DriveRover'
Conclusion
We've followed again the TDD methodology: write the test, then the code. We took care of describing first the happy scenario and then unhappy scenarios to cover all the cases.
We've also used the same twist on the Command Bus pattern: we created a Command class that describes the use case (drive the rover) and does a simple validation on the input.
What's next
In the next article, we'll proceed to the third step of TDD: refactoring
DriveRover
by extracting Instruction
in its own class.