RSS Amplifier

Loïc Faugeron · Aug 9, 2016

Mars Rover, Driving

0
Sign in to vote or save

This page cannot be shown here. You can still read it on the original site — the toolbar below keeps your place in the directory.

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…

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 the x or y axis) or turn_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.

Read on /2016/08/10/mars-rover-driving.html

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.