Mars Rover Kata using TDD and SOLID

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
7
down vote

favorite
2












I am doing MarsRoverKata exercise just to train my coding skills and I came up with the following solution.




A squad of robotic rovers are to be landed by NASA on a plateau on
Mars.



This plateau, which is curiously rectangular, must be navigated by the
rovers so that their on board cameras can get a complete view of the
surrounding terrain to send back to Earth. A rover's position is
represented by a combination of an x and y co-ordinates and a letter
representing one of the four cardinal compass points. The plateau is
divided up into a grid to simplify navigation. An example position
might be 0, 0, N, which means the rover is in the bottom left corner
and facing North.



In order to control a rover, NASA sends a simple string of letters.
The possible letters are 'L', 'R' and 'M'. 'L' and 'R' makes the rover
spin 90 degrees left or right respectively, without moving from its
current spot. 'M' means move forward one grid point, and maintain the
same heading. Assume that the square directly North from (x, y) is (x, y+1).



Input (whether hard coded or input from keyboard): The first line of
input is the upper-right coordinates of the plateau, the lower-left
coordinates are assumed to be 0,0. The rest of the input is
information pertaining to the rovers that have been deployed. Each
rover has two lines of input. The first line gives the rover's
position, and the second line is a series of instructions telling the
rover how to explore the plateau.



The position is made up of two integers and a letter separated by
spaces, corresponding to the x and y co-ordinates and the rover's
orientation. Each rover will be finished sequentially, which means
that the second rover won't start to move until the first one has
finished moving. Output: The output for each rover should be its final
co-ordinates and heading.



Plateau max X and Y, Starting coordinates, direction and path for two
rovers:



 5 5
1 2 N
LMLMLMLMM
3 3 E
MMRMMRMRRM


Output and new coordinates:



1 3 N
5 1 E



I was trying to follow SOLID principles in my implementation and I used TDD approach to write code.
Please criticize.



I have two main classes: MarsRover (manages main parameters of the Rover like initial position and final position) and MarsRoverNavigator (responsible for movements and spinning).



MarsRover.cs:



public class MarsRover

private readonly string input;
private MarsRoverNavigator marsRoverNavigator;

public MarsRover(string input)

this.input = input;


public NavigationParameters NavigationParameters get; private set;
public string FinalPosition get; private set;

public void Initialize()

NavigationParameters = InputValidator.GetNaviagtionParametersFromInput(input);


public void Navigate()

marsRoverNavigator = new MarsRoverNavigator(NavigationParameters);
FinalPosition = marsRoverNavigator.Navigate();




MarsRoverNavigator.cs:



public class MarsRoverNavigator

private readonly NavigationParameters navigationParameters;
private SpinningControl spinningControl;
private MovingControl movingControl;

public MarsRoverNavigator(NavigationParameters navigationParameters)

this.navigationParameters = navigationParameters;
spinningControl = new SpinningControl();
movingControl = new MovingControl();


public string Navigate()

var command = navigationParameters.Command;

foreach (var step in command)

DoAStep(step);


var result = $"navigationParameters.CurrentCoordinates.X navigationParameters.CurrentCoordinates.Y navigationParameters.CurrentDirection";

return result;


private void DoAStep(char stepCommand)




NavigationParameters.cs:



public class NavigationParameters
{
public string CurrentDirection get; private set;
public string Command get;
public Coordinates PlateauDimenstions get;
public Coordinates CurrentCoordinates get; private set;

public NavigationParameters(string currentDirection, Coordinates plateauDimenstions, Coordinates currentCoordinates, string command)

CurrentDirection = currentDirection;
PlateauDimenstions = plateauDimenstions;
CurrentCoordinates = currentCoordinates;
Command = command;


public void UpdateCurrentDirection(string newDirection)

CurrentDirection = newDirection;


internal void UpdateCurrentCoordinates(Coordinates newCoordinates)

CurrentCoordinates = newCoordinates;



MovingControl.cs is implemented as a dictionary:



public class MovingControl

public Dictionary<string, Func<Coordinates, Coordinates>> MoveFunctions =
new Dictionary<string, Func<Coordinates, Coordinates>>

"N", MoveNorth,
"W", MoveWest,
"S", MoveSouth,
"E", MoveEast
;

public Coordinates Move(char command, string currentDirection, Coordinates currentCoordinates)

if (command == 'M')

return MoveFunctions[currentDirection](currentCoordinates);


return currentCoordinates;


private static Coordinates MoveEast(Coordinates coordinates)

return new Coordinates()

X = coordinates.X + 1,
Y = coordinates.Y
;


private static Coordinates MoveSouth(Coordinates coordinates)

return new Coordinates()

X = coordinates.X,
Y = coordinates.Y - 1
;


private static Coordinates MoveWest(Coordinates coordinates)

return new Coordinates()

X = coordinates.X - 1,
Y = coordinates.Y
;


private static Coordinates MoveNorth(Coordinates coordinates)

return new Coordinates()

X = coordinates.X,
Y = coordinates.Y + 1
;




SpinningControl.cs is implemented as a Circular LinkedList:



public class SpinningControl

static readonly LinkedList<string> directions =
new LinkedList<string>(new "N", "W", "S", "E" );

public readonly Dictionary<char, Func<string, string>> SpinningFunctions =
new Dictionary<char, Func<string, string>>

'L', TurnLeft,
'R', TurnRight,
'M', Stay
;

public string GetNextDirection(string currentDirection, char stepCommand)

return SpinningFunctions[stepCommand](currentDirection);


private static string TurnRight(string currentDirection)

LinkedListNode<string> currentIndex = directions.Find(currentDirection);
return currentIndex.PreviousOrLast().Value;


private static string TurnLeft(string currentDirection)

LinkedListNode<string> currentIndex = directions.Find(currentDirection);
return currentIndex.NextOrFirst().Value;


private static string Stay(string currentDirection)

return currentDirection;




Circular LinkedList extension:



public static class CircularLinkedList

public static LinkedListNode<T> NextOrFirst<T>(this LinkedListNode<T> current)

return current.Next ?? current.List.First;


public static LinkedListNode<T> PreviousOrLast<T>(this LinkedListNode<T> current)

return current.Previous ?? current.List.Last;




InputValidator.cs:



public static class InputValidator

private static Coordinates plateauDimenstions;
private static Coordinates currentCoordinates;
private static string currentDirection;
private static string command;

private static string inputByLines;

private const int expectedNumberOfInputLines = 3;
private const int expectedLineWithPlateauDimension = 0;
private const int expectedLineWithStartPosition = 1;
private const int expectedLineWithCommand = 2;

private const char linesDelimeter = 'n';
private const char parametersDelimeter = ' ';

private static readonly List<string> allowedDirections = new List<string> "N", "W", "E", "S" ;

public static NavigationParameters GetNaviagtionParametersFromInput(string input)

SplitInputByLines(input);
SetPlateauDimensions(inputByLines);
SetStartPositionAndDirection(inputByLines);
SetCommand();

return new NavigationParameters(currentDirection, plateauDimenstions, currentCoordinates, command);


private static void SplitInputByLines(string input)

var splitString = input.Split(linesDelimeter);

if (splitString.Length != expectedNumberOfInputLines)

throw new IncorrectInputFormatException();


inputByLines = splitString;


private static void SetPlateauDimensions(string inputLines)

var stringPlateauDimenstions = inputLines[expectedLineWithPlateauDimension].Split(parametersDelimeter);

if (PlateauDimensionsAreInvalid(stringPlateauDimenstions))

throw new IncorrectPlateauDimensionsException();


plateauDimenstions = new Coordinates

X = Int32.Parse(stringPlateauDimenstions[0]),
Y = Int32.Parse(stringPlateauDimenstions[1])
;


private static void SetStartPositionAndDirection(string inputByLines)

var stringCurrentPositionAndDirection = inputByLines[expectedLineWithStartPosition].Split(parametersDelimeter);

if (StartPositionIsInvalid(stringCurrentPositionAndDirection))

throw new IncorrectStartPositionException();


currentCoordinates = new Coordinates

X = Int32.Parse(stringCurrentPositionAndDirection[0]),
Y = Int32.Parse(stringCurrentPositionAndDirection[1])
;

currentDirection = stringCurrentPositionAndDirection[2];


private static void SetCommand()

command = inputByLines[expectedLineWithCommand];


private static bool StartPositionIsInvalid(string stringCurrentPositionAndDirection)


private static bool PlateauDimensionsAreInvalid(string stringPlateauDimenstions)




Tests around MarsRoverNavigator:



[TestFixture]
public class MarsRoverNavigatorShould

[TestCase("5 5n0 0 NnL", "0 0 W")]
[TestCase("5 5n0 0 NnR", "0 0 E")]
[TestCase("5 5n0 0 WnL", "0 0 S")]
[TestCase("5 5n0 0 WnR", "0 0 N")]
[TestCase("5 5n0 0 SnL", "0 0 E")]
[TestCase("5 5n0 0 SnR", "0 0 W")]
[TestCase("5 5n0 0 EnL", "0 0 N")]
[TestCase("5 5n0 0 EnR", "0 0 S")]
[TestCase("5 5n1 1 NnM", "1 2 N")]
[TestCase("5 5n1 1 WnM", "0 1 W")]
[TestCase("5 5n1 1 SnM", "1 0 S")]
[TestCase("5 5n1 1 EnM", "2 1 E")]
public void UpdateDirectionWhenPassSpinDirections(string input, string expectedDirection)

var marsRover = new MarsRover(input);
marsRover.Initialize();
marsRover.Navigate();

var actualResult = marsRover.FinalPosition;

actualResult.Should().BeEquivalentTo(expectedDirection);


[TestCase("5 5n0 0 NnM", "0 1 N")]
[TestCase("5 5n1 1 NnMLMR", "0 2 N")]
[TestCase("5 5n1 1 WnMLMLMLM", "1 1 N")]
[TestCase("5 5n0 0 NnMMMMM", "0 5 N")]
[TestCase("5 5n0 0 EnMMMMM", "5 0 E")]
[TestCase("5 5n0 0 NnRMLMRMLMRMLMRMLM", "4 4 N")]
public void UpdatePositionWhenPassCorrectInput(string input, string expectedPosition)

var marsRover = new MarsRover(input);
marsRover.Initialize();
marsRover.Navigate();

var actualResult = marsRover.FinalPosition;

actualResult.Should().BeEquivalentTo(expectedPosition);


[TestCase("1 1n0 0 NnMM")]
[TestCase("1 1n0 0 EnMM")]
public void ReturnExceptionWhenCommandSendsRoverOutOfPlateau(string input)

var marsRover = new MarsRover(input);
marsRover.Initialize();

marsRover.Invoking(y => y.Navigate())
.Should().Throw<InvalidCommandException>()
.WithMessage("Command is invalid: Rover is sent outside the Plateau");




Tests around input:



[TestFixture]
public class MarsRoverShould

[TestCase("5 5n0 0 NnM", 5, 5, 0, 0, "N", "M")]
[TestCase("10 10n5 9 EnLMLMLM", 10, 10, 5, 9, "E", "LMLMLM")]
public void ParseAnInputCorrectly(string input, int expectedXPlateauDimension, int expectedYPlateauDimension,
int expectedXStartPosition, int expectedYStartPosition, string expectedDirection, string expectedCommand)

var expectedPlateausDimensions = new Coordinates() X = expectedXPlateauDimension, Y = expectedYPlateauDimension ;
var expectedStartingPosition = new Coordinates() X = expectedXStartPosition, Y = expectedYStartPosition ;

var expectedNavigationParameters = new NavigationParameters(expectedDirection, expectedPlateausDimensions,
expectedStartingPosition, expectedCommand);

var marsRover = new MarsRover(input);
marsRover.Initialize();
var actualResult = marsRover.NavigationParameters;

actualResult.Should().BeEquivalentTo(expectedNavigationParameters);


[TestCase("10 10 5n1 9 EnLMLMLM")]
[TestCase("10n5 9 EnLMLMLM")]
[TestCase("10 An5 9 EnLMLMLM")]
public void ReturnExceptionWhenWrongPlateauDimensionsInput(string input)

var marsRover = new MarsRover(input);

marsRover.Invoking(y => y.Initialize())
.Should().Throw<IncorrectPlateauDimensionsException>()
.WithMessage("Plateau dimensions should contain two int parameters: x and y");


[TestCase("1 1n1 1nLMLMLM")]
[TestCase("1 1n1 NnLMLMLM")]
[TestCase("1 1n1nLMLMLM")]
[TestCase("5 5n5 A NnLMLMLM")]
[TestCase("5 5n5 1 AnLMLMLM")]
[TestCase("1 1n5 1 NnLMLMLM")]
public void ReturnExceptionWhenWrongStartPositionInput(string input)

var marsRover = new MarsRover(input);

marsRover.Invoking(y => y.Initialize())
.Should().Throw<IncorrectStartPositionException>()
.WithMessage("Start position and direction should contain three parameters: int x, int y and direction (N, S, W or E)");


[TestCase("10 10; 5 9; LMLMLM")]
[TestCase("10 10nLMLMLM")]
public void ReturnExceptionWhenWrongInputFormat(string input)

var marsRover = new MarsRover(input);

marsRover.Invoking(y => y.Initialize())
.Should().Throw<IncorrectInputFormatException>()
.WithMessage("Error occured while splitting the input: format is incorrect");








share|improve this question



























    up vote
    7
    down vote

    favorite
    2












    I am doing MarsRoverKata exercise just to train my coding skills and I came up with the following solution.




    A squad of robotic rovers are to be landed by NASA on a plateau on
    Mars.



    This plateau, which is curiously rectangular, must be navigated by the
    rovers so that their on board cameras can get a complete view of the
    surrounding terrain to send back to Earth. A rover's position is
    represented by a combination of an x and y co-ordinates and a letter
    representing one of the four cardinal compass points. The plateau is
    divided up into a grid to simplify navigation. An example position
    might be 0, 0, N, which means the rover is in the bottom left corner
    and facing North.



    In order to control a rover, NASA sends a simple string of letters.
    The possible letters are 'L', 'R' and 'M'. 'L' and 'R' makes the rover
    spin 90 degrees left or right respectively, without moving from its
    current spot. 'M' means move forward one grid point, and maintain the
    same heading. Assume that the square directly North from (x, y) is (x, y+1).



    Input (whether hard coded or input from keyboard): The first line of
    input is the upper-right coordinates of the plateau, the lower-left
    coordinates are assumed to be 0,0. The rest of the input is
    information pertaining to the rovers that have been deployed. Each
    rover has two lines of input. The first line gives the rover's
    position, and the second line is a series of instructions telling the
    rover how to explore the plateau.



    The position is made up of two integers and a letter separated by
    spaces, corresponding to the x and y co-ordinates and the rover's
    orientation. Each rover will be finished sequentially, which means
    that the second rover won't start to move until the first one has
    finished moving. Output: The output for each rover should be its final
    co-ordinates and heading.



    Plateau max X and Y, Starting coordinates, direction and path for two
    rovers:



     5 5
    1 2 N
    LMLMLMLMM
    3 3 E
    MMRMMRMRRM


    Output and new coordinates:



    1 3 N
    5 1 E



    I was trying to follow SOLID principles in my implementation and I used TDD approach to write code.
    Please criticize.



    I have two main classes: MarsRover (manages main parameters of the Rover like initial position and final position) and MarsRoverNavigator (responsible for movements and spinning).



    MarsRover.cs:



    public class MarsRover

    private readonly string input;
    private MarsRoverNavigator marsRoverNavigator;

    public MarsRover(string input)

    this.input = input;


    public NavigationParameters NavigationParameters get; private set;
    public string FinalPosition get; private set;

    public void Initialize()

    NavigationParameters = InputValidator.GetNaviagtionParametersFromInput(input);


    public void Navigate()

    marsRoverNavigator = new MarsRoverNavigator(NavigationParameters);
    FinalPosition = marsRoverNavigator.Navigate();




    MarsRoverNavigator.cs:



    public class MarsRoverNavigator

    private readonly NavigationParameters navigationParameters;
    private SpinningControl spinningControl;
    private MovingControl movingControl;

    public MarsRoverNavigator(NavigationParameters navigationParameters)

    this.navigationParameters = navigationParameters;
    spinningControl = new SpinningControl();
    movingControl = new MovingControl();


    public string Navigate()

    var command = navigationParameters.Command;

    foreach (var step in command)

    DoAStep(step);


    var result = $"navigationParameters.CurrentCoordinates.X navigationParameters.CurrentCoordinates.Y navigationParameters.CurrentDirection";

    return result;


    private void DoAStep(char stepCommand)




    NavigationParameters.cs:



    public class NavigationParameters
    {
    public string CurrentDirection get; private set;
    public string Command get;
    public Coordinates PlateauDimenstions get;
    public Coordinates CurrentCoordinates get; private set;

    public NavigationParameters(string currentDirection, Coordinates plateauDimenstions, Coordinates currentCoordinates, string command)

    CurrentDirection = currentDirection;
    PlateauDimenstions = plateauDimenstions;
    CurrentCoordinates = currentCoordinates;
    Command = command;


    public void UpdateCurrentDirection(string newDirection)

    CurrentDirection = newDirection;


    internal void UpdateCurrentCoordinates(Coordinates newCoordinates)

    CurrentCoordinates = newCoordinates;



    MovingControl.cs is implemented as a dictionary:



    public class MovingControl

    public Dictionary<string, Func<Coordinates, Coordinates>> MoveFunctions =
    new Dictionary<string, Func<Coordinates, Coordinates>>

    "N", MoveNorth,
    "W", MoveWest,
    "S", MoveSouth,
    "E", MoveEast
    ;

    public Coordinates Move(char command, string currentDirection, Coordinates currentCoordinates)

    if (command == 'M')

    return MoveFunctions[currentDirection](currentCoordinates);


    return currentCoordinates;


    private static Coordinates MoveEast(Coordinates coordinates)

    return new Coordinates()

    X = coordinates.X + 1,
    Y = coordinates.Y
    ;


    private static Coordinates MoveSouth(Coordinates coordinates)

    return new Coordinates()

    X = coordinates.X,
    Y = coordinates.Y - 1
    ;


    private static Coordinates MoveWest(Coordinates coordinates)

    return new Coordinates()

    X = coordinates.X - 1,
    Y = coordinates.Y
    ;


    private static Coordinates MoveNorth(Coordinates coordinates)

    return new Coordinates()

    X = coordinates.X,
    Y = coordinates.Y + 1
    ;




    SpinningControl.cs is implemented as a Circular LinkedList:



    public class SpinningControl

    static readonly LinkedList<string> directions =
    new LinkedList<string>(new "N", "W", "S", "E" );

    public readonly Dictionary<char, Func<string, string>> SpinningFunctions =
    new Dictionary<char, Func<string, string>>

    'L', TurnLeft,
    'R', TurnRight,
    'M', Stay
    ;

    public string GetNextDirection(string currentDirection, char stepCommand)

    return SpinningFunctions[stepCommand](currentDirection);


    private static string TurnRight(string currentDirection)

    LinkedListNode<string> currentIndex = directions.Find(currentDirection);
    return currentIndex.PreviousOrLast().Value;


    private static string TurnLeft(string currentDirection)

    LinkedListNode<string> currentIndex = directions.Find(currentDirection);
    return currentIndex.NextOrFirst().Value;


    private static string Stay(string currentDirection)

    return currentDirection;




    Circular LinkedList extension:



    public static class CircularLinkedList

    public static LinkedListNode<T> NextOrFirst<T>(this LinkedListNode<T> current)

    return current.Next ?? current.List.First;


    public static LinkedListNode<T> PreviousOrLast<T>(this LinkedListNode<T> current)

    return current.Previous ?? current.List.Last;




    InputValidator.cs:



    public static class InputValidator

    private static Coordinates plateauDimenstions;
    private static Coordinates currentCoordinates;
    private static string currentDirection;
    private static string command;

    private static string inputByLines;

    private const int expectedNumberOfInputLines = 3;
    private const int expectedLineWithPlateauDimension = 0;
    private const int expectedLineWithStartPosition = 1;
    private const int expectedLineWithCommand = 2;

    private const char linesDelimeter = 'n';
    private const char parametersDelimeter = ' ';

    private static readonly List<string> allowedDirections = new List<string> "N", "W", "E", "S" ;

    public static NavigationParameters GetNaviagtionParametersFromInput(string input)

    SplitInputByLines(input);
    SetPlateauDimensions(inputByLines);
    SetStartPositionAndDirection(inputByLines);
    SetCommand();

    return new NavigationParameters(currentDirection, plateauDimenstions, currentCoordinates, command);


    private static void SplitInputByLines(string input)

    var splitString = input.Split(linesDelimeter);

    if (splitString.Length != expectedNumberOfInputLines)

    throw new IncorrectInputFormatException();


    inputByLines = splitString;


    private static void SetPlateauDimensions(string inputLines)

    var stringPlateauDimenstions = inputLines[expectedLineWithPlateauDimension].Split(parametersDelimeter);

    if (PlateauDimensionsAreInvalid(stringPlateauDimenstions))

    throw new IncorrectPlateauDimensionsException();


    plateauDimenstions = new Coordinates

    X = Int32.Parse(stringPlateauDimenstions[0]),
    Y = Int32.Parse(stringPlateauDimenstions[1])
    ;


    private static void SetStartPositionAndDirection(string inputByLines)

    var stringCurrentPositionAndDirection = inputByLines[expectedLineWithStartPosition].Split(parametersDelimeter);

    if (StartPositionIsInvalid(stringCurrentPositionAndDirection))

    throw new IncorrectStartPositionException();


    currentCoordinates = new Coordinates

    X = Int32.Parse(stringCurrentPositionAndDirection[0]),
    Y = Int32.Parse(stringCurrentPositionAndDirection[1])
    ;

    currentDirection = stringCurrentPositionAndDirection[2];


    private static void SetCommand()

    command = inputByLines[expectedLineWithCommand];


    private static bool StartPositionIsInvalid(string stringCurrentPositionAndDirection)


    private static bool PlateauDimensionsAreInvalid(string stringPlateauDimenstions)




    Tests around MarsRoverNavigator:



    [TestFixture]
    public class MarsRoverNavigatorShould

    [TestCase("5 5n0 0 NnL", "0 0 W")]
    [TestCase("5 5n0 0 NnR", "0 0 E")]
    [TestCase("5 5n0 0 WnL", "0 0 S")]
    [TestCase("5 5n0 0 WnR", "0 0 N")]
    [TestCase("5 5n0 0 SnL", "0 0 E")]
    [TestCase("5 5n0 0 SnR", "0 0 W")]
    [TestCase("5 5n0 0 EnL", "0 0 N")]
    [TestCase("5 5n0 0 EnR", "0 0 S")]
    [TestCase("5 5n1 1 NnM", "1 2 N")]
    [TestCase("5 5n1 1 WnM", "0 1 W")]
    [TestCase("5 5n1 1 SnM", "1 0 S")]
    [TestCase("5 5n1 1 EnM", "2 1 E")]
    public void UpdateDirectionWhenPassSpinDirections(string input, string expectedDirection)

    var marsRover = new MarsRover(input);
    marsRover.Initialize();
    marsRover.Navigate();

    var actualResult = marsRover.FinalPosition;

    actualResult.Should().BeEquivalentTo(expectedDirection);


    [TestCase("5 5n0 0 NnM", "0 1 N")]
    [TestCase("5 5n1 1 NnMLMR", "0 2 N")]
    [TestCase("5 5n1 1 WnMLMLMLM", "1 1 N")]
    [TestCase("5 5n0 0 NnMMMMM", "0 5 N")]
    [TestCase("5 5n0 0 EnMMMMM", "5 0 E")]
    [TestCase("5 5n0 0 NnRMLMRMLMRMLMRMLM", "4 4 N")]
    public void UpdatePositionWhenPassCorrectInput(string input, string expectedPosition)

    var marsRover = new MarsRover(input);
    marsRover.Initialize();
    marsRover.Navigate();

    var actualResult = marsRover.FinalPosition;

    actualResult.Should().BeEquivalentTo(expectedPosition);


    [TestCase("1 1n0 0 NnMM")]
    [TestCase("1 1n0 0 EnMM")]
    public void ReturnExceptionWhenCommandSendsRoverOutOfPlateau(string input)

    var marsRover = new MarsRover(input);
    marsRover.Initialize();

    marsRover.Invoking(y => y.Navigate())
    .Should().Throw<InvalidCommandException>()
    .WithMessage("Command is invalid: Rover is sent outside the Plateau");




    Tests around input:



    [TestFixture]
    public class MarsRoverShould

    [TestCase("5 5n0 0 NnM", 5, 5, 0, 0, "N", "M")]
    [TestCase("10 10n5 9 EnLMLMLM", 10, 10, 5, 9, "E", "LMLMLM")]
    public void ParseAnInputCorrectly(string input, int expectedXPlateauDimension, int expectedYPlateauDimension,
    int expectedXStartPosition, int expectedYStartPosition, string expectedDirection, string expectedCommand)

    var expectedPlateausDimensions = new Coordinates() X = expectedXPlateauDimension, Y = expectedYPlateauDimension ;
    var expectedStartingPosition = new Coordinates() X = expectedXStartPosition, Y = expectedYStartPosition ;

    var expectedNavigationParameters = new NavigationParameters(expectedDirection, expectedPlateausDimensions,
    expectedStartingPosition, expectedCommand);

    var marsRover = new MarsRover(input);
    marsRover.Initialize();
    var actualResult = marsRover.NavigationParameters;

    actualResult.Should().BeEquivalentTo(expectedNavigationParameters);


    [TestCase("10 10 5n1 9 EnLMLMLM")]
    [TestCase("10n5 9 EnLMLMLM")]
    [TestCase("10 An5 9 EnLMLMLM")]
    public void ReturnExceptionWhenWrongPlateauDimensionsInput(string input)

    var marsRover = new MarsRover(input);

    marsRover.Invoking(y => y.Initialize())
    .Should().Throw<IncorrectPlateauDimensionsException>()
    .WithMessage("Plateau dimensions should contain two int parameters: x and y");


    [TestCase("1 1n1 1nLMLMLM")]
    [TestCase("1 1n1 NnLMLMLM")]
    [TestCase("1 1n1nLMLMLM")]
    [TestCase("5 5n5 A NnLMLMLM")]
    [TestCase("5 5n5 1 AnLMLMLM")]
    [TestCase("1 1n5 1 NnLMLMLM")]
    public void ReturnExceptionWhenWrongStartPositionInput(string input)

    var marsRover = new MarsRover(input);

    marsRover.Invoking(y => y.Initialize())
    .Should().Throw<IncorrectStartPositionException>()
    .WithMessage("Start position and direction should contain three parameters: int x, int y and direction (N, S, W or E)");


    [TestCase("10 10; 5 9; LMLMLM")]
    [TestCase("10 10nLMLMLM")]
    public void ReturnExceptionWhenWrongInputFormat(string input)

    var marsRover = new MarsRover(input);

    marsRover.Invoking(y => y.Initialize())
    .Should().Throw<IncorrectInputFormatException>()
    .WithMessage("Error occured while splitting the input: format is incorrect");








    share|improve this question























      up vote
      7
      down vote

      favorite
      2









      up vote
      7
      down vote

      favorite
      2






      2





      I am doing MarsRoverKata exercise just to train my coding skills and I came up with the following solution.




      A squad of robotic rovers are to be landed by NASA on a plateau on
      Mars.



      This plateau, which is curiously rectangular, must be navigated by the
      rovers so that their on board cameras can get a complete view of the
      surrounding terrain to send back to Earth. A rover's position is
      represented by a combination of an x and y co-ordinates and a letter
      representing one of the four cardinal compass points. The plateau is
      divided up into a grid to simplify navigation. An example position
      might be 0, 0, N, which means the rover is in the bottom left corner
      and facing North.



      In order to control a rover, NASA sends a simple string of letters.
      The possible letters are 'L', 'R' and 'M'. 'L' and 'R' makes the rover
      spin 90 degrees left or right respectively, without moving from its
      current spot. 'M' means move forward one grid point, and maintain the
      same heading. Assume that the square directly North from (x, y) is (x, y+1).



      Input (whether hard coded or input from keyboard): The first line of
      input is the upper-right coordinates of the plateau, the lower-left
      coordinates are assumed to be 0,0. The rest of the input is
      information pertaining to the rovers that have been deployed. Each
      rover has two lines of input. The first line gives the rover's
      position, and the second line is a series of instructions telling the
      rover how to explore the plateau.



      The position is made up of two integers and a letter separated by
      spaces, corresponding to the x and y co-ordinates and the rover's
      orientation. Each rover will be finished sequentially, which means
      that the second rover won't start to move until the first one has
      finished moving. Output: The output for each rover should be its final
      co-ordinates and heading.



      Plateau max X and Y, Starting coordinates, direction and path for two
      rovers:



       5 5
      1 2 N
      LMLMLMLMM
      3 3 E
      MMRMMRMRRM


      Output and new coordinates:



      1 3 N
      5 1 E



      I was trying to follow SOLID principles in my implementation and I used TDD approach to write code.
      Please criticize.



      I have two main classes: MarsRover (manages main parameters of the Rover like initial position and final position) and MarsRoverNavigator (responsible for movements and spinning).



      MarsRover.cs:



      public class MarsRover

      private readonly string input;
      private MarsRoverNavigator marsRoverNavigator;

      public MarsRover(string input)

      this.input = input;


      public NavigationParameters NavigationParameters get; private set;
      public string FinalPosition get; private set;

      public void Initialize()

      NavigationParameters = InputValidator.GetNaviagtionParametersFromInput(input);


      public void Navigate()

      marsRoverNavigator = new MarsRoverNavigator(NavigationParameters);
      FinalPosition = marsRoverNavigator.Navigate();




      MarsRoverNavigator.cs:



      public class MarsRoverNavigator

      private readonly NavigationParameters navigationParameters;
      private SpinningControl spinningControl;
      private MovingControl movingControl;

      public MarsRoverNavigator(NavigationParameters navigationParameters)

      this.navigationParameters = navigationParameters;
      spinningControl = new SpinningControl();
      movingControl = new MovingControl();


      public string Navigate()

      var command = navigationParameters.Command;

      foreach (var step in command)

      DoAStep(step);


      var result = $"navigationParameters.CurrentCoordinates.X navigationParameters.CurrentCoordinates.Y navigationParameters.CurrentDirection";

      return result;


      private void DoAStep(char stepCommand)




      NavigationParameters.cs:



      public class NavigationParameters
      {
      public string CurrentDirection get; private set;
      public string Command get;
      public Coordinates PlateauDimenstions get;
      public Coordinates CurrentCoordinates get; private set;

      public NavigationParameters(string currentDirection, Coordinates plateauDimenstions, Coordinates currentCoordinates, string command)

      CurrentDirection = currentDirection;
      PlateauDimenstions = plateauDimenstions;
      CurrentCoordinates = currentCoordinates;
      Command = command;


      public void UpdateCurrentDirection(string newDirection)

      CurrentDirection = newDirection;


      internal void UpdateCurrentCoordinates(Coordinates newCoordinates)

      CurrentCoordinates = newCoordinates;



      MovingControl.cs is implemented as a dictionary:



      public class MovingControl

      public Dictionary<string, Func<Coordinates, Coordinates>> MoveFunctions =
      new Dictionary<string, Func<Coordinates, Coordinates>>

      "N", MoveNorth,
      "W", MoveWest,
      "S", MoveSouth,
      "E", MoveEast
      ;

      public Coordinates Move(char command, string currentDirection, Coordinates currentCoordinates)

      if (command == 'M')

      return MoveFunctions[currentDirection](currentCoordinates);


      return currentCoordinates;


      private static Coordinates MoveEast(Coordinates coordinates)

      return new Coordinates()

      X = coordinates.X + 1,
      Y = coordinates.Y
      ;


      private static Coordinates MoveSouth(Coordinates coordinates)

      return new Coordinates()

      X = coordinates.X,
      Y = coordinates.Y - 1
      ;


      private static Coordinates MoveWest(Coordinates coordinates)

      return new Coordinates()

      X = coordinates.X - 1,
      Y = coordinates.Y
      ;


      private static Coordinates MoveNorth(Coordinates coordinates)

      return new Coordinates()

      X = coordinates.X,
      Y = coordinates.Y + 1
      ;




      SpinningControl.cs is implemented as a Circular LinkedList:



      public class SpinningControl

      static readonly LinkedList<string> directions =
      new LinkedList<string>(new "N", "W", "S", "E" );

      public readonly Dictionary<char, Func<string, string>> SpinningFunctions =
      new Dictionary<char, Func<string, string>>

      'L', TurnLeft,
      'R', TurnRight,
      'M', Stay
      ;

      public string GetNextDirection(string currentDirection, char stepCommand)

      return SpinningFunctions[stepCommand](currentDirection);


      private static string TurnRight(string currentDirection)

      LinkedListNode<string> currentIndex = directions.Find(currentDirection);
      return currentIndex.PreviousOrLast().Value;


      private static string TurnLeft(string currentDirection)

      LinkedListNode<string> currentIndex = directions.Find(currentDirection);
      return currentIndex.NextOrFirst().Value;


      private static string Stay(string currentDirection)

      return currentDirection;




      Circular LinkedList extension:



      public static class CircularLinkedList

      public static LinkedListNode<T> NextOrFirst<T>(this LinkedListNode<T> current)

      return current.Next ?? current.List.First;


      public static LinkedListNode<T> PreviousOrLast<T>(this LinkedListNode<T> current)

      return current.Previous ?? current.List.Last;




      InputValidator.cs:



      public static class InputValidator

      private static Coordinates plateauDimenstions;
      private static Coordinates currentCoordinates;
      private static string currentDirection;
      private static string command;

      private static string inputByLines;

      private const int expectedNumberOfInputLines = 3;
      private const int expectedLineWithPlateauDimension = 0;
      private const int expectedLineWithStartPosition = 1;
      private const int expectedLineWithCommand = 2;

      private const char linesDelimeter = 'n';
      private const char parametersDelimeter = ' ';

      private static readonly List<string> allowedDirections = new List<string> "N", "W", "E", "S" ;

      public static NavigationParameters GetNaviagtionParametersFromInput(string input)

      SplitInputByLines(input);
      SetPlateauDimensions(inputByLines);
      SetStartPositionAndDirection(inputByLines);
      SetCommand();

      return new NavigationParameters(currentDirection, plateauDimenstions, currentCoordinates, command);


      private static void SplitInputByLines(string input)

      var splitString = input.Split(linesDelimeter);

      if (splitString.Length != expectedNumberOfInputLines)

      throw new IncorrectInputFormatException();


      inputByLines = splitString;


      private static void SetPlateauDimensions(string inputLines)

      var stringPlateauDimenstions = inputLines[expectedLineWithPlateauDimension].Split(parametersDelimeter);

      if (PlateauDimensionsAreInvalid(stringPlateauDimenstions))

      throw new IncorrectPlateauDimensionsException();


      plateauDimenstions = new Coordinates

      X = Int32.Parse(stringPlateauDimenstions[0]),
      Y = Int32.Parse(stringPlateauDimenstions[1])
      ;


      private static void SetStartPositionAndDirection(string inputByLines)

      var stringCurrentPositionAndDirection = inputByLines[expectedLineWithStartPosition].Split(parametersDelimeter);

      if (StartPositionIsInvalid(stringCurrentPositionAndDirection))

      throw new IncorrectStartPositionException();


      currentCoordinates = new Coordinates

      X = Int32.Parse(stringCurrentPositionAndDirection[0]),
      Y = Int32.Parse(stringCurrentPositionAndDirection[1])
      ;

      currentDirection = stringCurrentPositionAndDirection[2];


      private static void SetCommand()

      command = inputByLines[expectedLineWithCommand];


      private static bool StartPositionIsInvalid(string stringCurrentPositionAndDirection)


      private static bool PlateauDimensionsAreInvalid(string stringPlateauDimenstions)




      Tests around MarsRoverNavigator:



      [TestFixture]
      public class MarsRoverNavigatorShould

      [TestCase("5 5n0 0 NnL", "0 0 W")]
      [TestCase("5 5n0 0 NnR", "0 0 E")]
      [TestCase("5 5n0 0 WnL", "0 0 S")]
      [TestCase("5 5n0 0 WnR", "0 0 N")]
      [TestCase("5 5n0 0 SnL", "0 0 E")]
      [TestCase("5 5n0 0 SnR", "0 0 W")]
      [TestCase("5 5n0 0 EnL", "0 0 N")]
      [TestCase("5 5n0 0 EnR", "0 0 S")]
      [TestCase("5 5n1 1 NnM", "1 2 N")]
      [TestCase("5 5n1 1 WnM", "0 1 W")]
      [TestCase("5 5n1 1 SnM", "1 0 S")]
      [TestCase("5 5n1 1 EnM", "2 1 E")]
      public void UpdateDirectionWhenPassSpinDirections(string input, string expectedDirection)

      var marsRover = new MarsRover(input);
      marsRover.Initialize();
      marsRover.Navigate();

      var actualResult = marsRover.FinalPosition;

      actualResult.Should().BeEquivalentTo(expectedDirection);


      [TestCase("5 5n0 0 NnM", "0 1 N")]
      [TestCase("5 5n1 1 NnMLMR", "0 2 N")]
      [TestCase("5 5n1 1 WnMLMLMLM", "1 1 N")]
      [TestCase("5 5n0 0 NnMMMMM", "0 5 N")]
      [TestCase("5 5n0 0 EnMMMMM", "5 0 E")]
      [TestCase("5 5n0 0 NnRMLMRMLMRMLMRMLM", "4 4 N")]
      public void UpdatePositionWhenPassCorrectInput(string input, string expectedPosition)

      var marsRover = new MarsRover(input);
      marsRover.Initialize();
      marsRover.Navigate();

      var actualResult = marsRover.FinalPosition;

      actualResult.Should().BeEquivalentTo(expectedPosition);


      [TestCase("1 1n0 0 NnMM")]
      [TestCase("1 1n0 0 EnMM")]
      public void ReturnExceptionWhenCommandSendsRoverOutOfPlateau(string input)

      var marsRover = new MarsRover(input);
      marsRover.Initialize();

      marsRover.Invoking(y => y.Navigate())
      .Should().Throw<InvalidCommandException>()
      .WithMessage("Command is invalid: Rover is sent outside the Plateau");




      Tests around input:



      [TestFixture]
      public class MarsRoverShould

      [TestCase("5 5n0 0 NnM", 5, 5, 0, 0, "N", "M")]
      [TestCase("10 10n5 9 EnLMLMLM", 10, 10, 5, 9, "E", "LMLMLM")]
      public void ParseAnInputCorrectly(string input, int expectedXPlateauDimension, int expectedYPlateauDimension,
      int expectedXStartPosition, int expectedYStartPosition, string expectedDirection, string expectedCommand)

      var expectedPlateausDimensions = new Coordinates() X = expectedXPlateauDimension, Y = expectedYPlateauDimension ;
      var expectedStartingPosition = new Coordinates() X = expectedXStartPosition, Y = expectedYStartPosition ;

      var expectedNavigationParameters = new NavigationParameters(expectedDirection, expectedPlateausDimensions,
      expectedStartingPosition, expectedCommand);

      var marsRover = new MarsRover(input);
      marsRover.Initialize();
      var actualResult = marsRover.NavigationParameters;

      actualResult.Should().BeEquivalentTo(expectedNavigationParameters);


      [TestCase("10 10 5n1 9 EnLMLMLM")]
      [TestCase("10n5 9 EnLMLMLM")]
      [TestCase("10 An5 9 EnLMLMLM")]
      public void ReturnExceptionWhenWrongPlateauDimensionsInput(string input)

      var marsRover = new MarsRover(input);

      marsRover.Invoking(y => y.Initialize())
      .Should().Throw<IncorrectPlateauDimensionsException>()
      .WithMessage("Plateau dimensions should contain two int parameters: x and y");


      [TestCase("1 1n1 1nLMLMLM")]
      [TestCase("1 1n1 NnLMLMLM")]
      [TestCase("1 1n1nLMLMLM")]
      [TestCase("5 5n5 A NnLMLMLM")]
      [TestCase("5 5n5 1 AnLMLMLM")]
      [TestCase("1 1n5 1 NnLMLMLM")]
      public void ReturnExceptionWhenWrongStartPositionInput(string input)

      var marsRover = new MarsRover(input);

      marsRover.Invoking(y => y.Initialize())
      .Should().Throw<IncorrectStartPositionException>()
      .WithMessage("Start position and direction should contain three parameters: int x, int y and direction (N, S, W or E)");


      [TestCase("10 10; 5 9; LMLMLM")]
      [TestCase("10 10nLMLMLM")]
      public void ReturnExceptionWhenWrongInputFormat(string input)

      var marsRover = new MarsRover(input);

      marsRover.Invoking(y => y.Initialize())
      .Should().Throw<IncorrectInputFormatException>()
      .WithMessage("Error occured while splitting the input: format is incorrect");








      share|improve this question













      I am doing MarsRoverKata exercise just to train my coding skills and I came up with the following solution.




      A squad of robotic rovers are to be landed by NASA on a plateau on
      Mars.



      This plateau, which is curiously rectangular, must be navigated by the
      rovers so that their on board cameras can get a complete view of the
      surrounding terrain to send back to Earth. A rover's position is
      represented by a combination of an x and y co-ordinates and a letter
      representing one of the four cardinal compass points. The plateau is
      divided up into a grid to simplify navigation. An example position
      might be 0, 0, N, which means the rover is in the bottom left corner
      and facing North.



      In order to control a rover, NASA sends a simple string of letters.
      The possible letters are 'L', 'R' and 'M'. 'L' and 'R' makes the rover
      spin 90 degrees left or right respectively, without moving from its
      current spot. 'M' means move forward one grid point, and maintain the
      same heading. Assume that the square directly North from (x, y) is (x, y+1).



      Input (whether hard coded or input from keyboard): The first line of
      input is the upper-right coordinates of the plateau, the lower-left
      coordinates are assumed to be 0,0. The rest of the input is
      information pertaining to the rovers that have been deployed. Each
      rover has two lines of input. The first line gives the rover's
      position, and the second line is a series of instructions telling the
      rover how to explore the plateau.



      The position is made up of two integers and a letter separated by
      spaces, corresponding to the x and y co-ordinates and the rover's
      orientation. Each rover will be finished sequentially, which means
      that the second rover won't start to move until the first one has
      finished moving. Output: The output for each rover should be its final
      co-ordinates and heading.



      Plateau max X and Y, Starting coordinates, direction and path for two
      rovers:



       5 5
      1 2 N
      LMLMLMLMM
      3 3 E
      MMRMMRMRRM


      Output and new coordinates:



      1 3 N
      5 1 E



      I was trying to follow SOLID principles in my implementation and I used TDD approach to write code.
      Please criticize.



      I have two main classes: MarsRover (manages main parameters of the Rover like initial position and final position) and MarsRoverNavigator (responsible for movements and spinning).



      MarsRover.cs:



      public class MarsRover

      private readonly string input;
      private MarsRoverNavigator marsRoverNavigator;

      public MarsRover(string input)

      this.input = input;


      public NavigationParameters NavigationParameters get; private set;
      public string FinalPosition get; private set;

      public void Initialize()

      NavigationParameters = InputValidator.GetNaviagtionParametersFromInput(input);


      public void Navigate()

      marsRoverNavigator = new MarsRoverNavigator(NavigationParameters);
      FinalPosition = marsRoverNavigator.Navigate();




      MarsRoverNavigator.cs:



      public class MarsRoverNavigator

      private readonly NavigationParameters navigationParameters;
      private SpinningControl spinningControl;
      private MovingControl movingControl;

      public MarsRoverNavigator(NavigationParameters navigationParameters)

      this.navigationParameters = navigationParameters;
      spinningControl = new SpinningControl();
      movingControl = new MovingControl();


      public string Navigate()

      var command = navigationParameters.Command;

      foreach (var step in command)

      DoAStep(step);


      var result = $"navigationParameters.CurrentCoordinates.X navigationParameters.CurrentCoordinates.Y navigationParameters.CurrentDirection";

      return result;


      private void DoAStep(char stepCommand)




      NavigationParameters.cs:



      public class NavigationParameters
      {
      public string CurrentDirection get; private set;
      public string Command get;
      public Coordinates PlateauDimenstions get;
      public Coordinates CurrentCoordinates get; private set;

      public NavigationParameters(string currentDirection, Coordinates plateauDimenstions, Coordinates currentCoordinates, string command)

      CurrentDirection = currentDirection;
      PlateauDimenstions = plateauDimenstions;
      CurrentCoordinates = currentCoordinates;
      Command = command;


      public void UpdateCurrentDirection(string newDirection)

      CurrentDirection = newDirection;


      internal void UpdateCurrentCoordinates(Coordinates newCoordinates)

      CurrentCoordinates = newCoordinates;



      MovingControl.cs is implemented as a dictionary:



      public class MovingControl

      public Dictionary<string, Func<Coordinates, Coordinates>> MoveFunctions =
      new Dictionary<string, Func<Coordinates, Coordinates>>

      "N", MoveNorth,
      "W", MoveWest,
      "S", MoveSouth,
      "E", MoveEast
      ;

      public Coordinates Move(char command, string currentDirection, Coordinates currentCoordinates)

      if (command == 'M')

      return MoveFunctions[currentDirection](currentCoordinates);


      return currentCoordinates;


      private static Coordinates MoveEast(Coordinates coordinates)

      return new Coordinates()

      X = coordinates.X + 1,
      Y = coordinates.Y
      ;


      private static Coordinates MoveSouth(Coordinates coordinates)

      return new Coordinates()

      X = coordinates.X,
      Y = coordinates.Y - 1
      ;


      private static Coordinates MoveWest(Coordinates coordinates)

      return new Coordinates()

      X = coordinates.X - 1,
      Y = coordinates.Y
      ;


      private static Coordinates MoveNorth(Coordinates coordinates)

      return new Coordinates()

      X = coordinates.X,
      Y = coordinates.Y + 1
      ;




      SpinningControl.cs is implemented as a Circular LinkedList:



      public class SpinningControl

      static readonly LinkedList<string> directions =
      new LinkedList<string>(new "N", "W", "S", "E" );

      public readonly Dictionary<char, Func<string, string>> SpinningFunctions =
      new Dictionary<char, Func<string, string>>

      'L', TurnLeft,
      'R', TurnRight,
      'M', Stay
      ;

      public string GetNextDirection(string currentDirection, char stepCommand)

      return SpinningFunctions[stepCommand](currentDirection);


      private static string TurnRight(string currentDirection)

      LinkedListNode<string> currentIndex = directions.Find(currentDirection);
      return currentIndex.PreviousOrLast().Value;


      private static string TurnLeft(string currentDirection)

      LinkedListNode<string> currentIndex = directions.Find(currentDirection);
      return currentIndex.NextOrFirst().Value;


      private static string Stay(string currentDirection)

      return currentDirection;




      Circular LinkedList extension:



      public static class CircularLinkedList

      public static LinkedListNode<T> NextOrFirst<T>(this LinkedListNode<T> current)

      return current.Next ?? current.List.First;


      public static LinkedListNode<T> PreviousOrLast<T>(this LinkedListNode<T> current)

      return current.Previous ?? current.List.Last;




      InputValidator.cs:



      public static class InputValidator

      private static Coordinates plateauDimenstions;
      private static Coordinates currentCoordinates;
      private static string currentDirection;
      private static string command;

      private static string inputByLines;

      private const int expectedNumberOfInputLines = 3;
      private const int expectedLineWithPlateauDimension = 0;
      private const int expectedLineWithStartPosition = 1;
      private const int expectedLineWithCommand = 2;

      private const char linesDelimeter = 'n';
      private const char parametersDelimeter = ' ';

      private static readonly List<string> allowedDirections = new List<string> "N", "W", "E", "S" ;

      public static NavigationParameters GetNaviagtionParametersFromInput(string input)

      SplitInputByLines(input);
      SetPlateauDimensions(inputByLines);
      SetStartPositionAndDirection(inputByLines);
      SetCommand();

      return new NavigationParameters(currentDirection, plateauDimenstions, currentCoordinates, command);


      private static void SplitInputByLines(string input)

      var splitString = input.Split(linesDelimeter);

      if (splitString.Length != expectedNumberOfInputLines)

      throw new IncorrectInputFormatException();


      inputByLines = splitString;


      private static void SetPlateauDimensions(string inputLines)

      var stringPlateauDimenstions = inputLines[expectedLineWithPlateauDimension].Split(parametersDelimeter);

      if (PlateauDimensionsAreInvalid(stringPlateauDimenstions))

      throw new IncorrectPlateauDimensionsException();


      plateauDimenstions = new Coordinates

      X = Int32.Parse(stringPlateauDimenstions[0]),
      Y = Int32.Parse(stringPlateauDimenstions[1])
      ;


      private static void SetStartPositionAndDirection(string inputByLines)

      var stringCurrentPositionAndDirection = inputByLines[expectedLineWithStartPosition].Split(parametersDelimeter);

      if (StartPositionIsInvalid(stringCurrentPositionAndDirection))

      throw new IncorrectStartPositionException();


      currentCoordinates = new Coordinates

      X = Int32.Parse(stringCurrentPositionAndDirection[0]),
      Y = Int32.Parse(stringCurrentPositionAndDirection[1])
      ;

      currentDirection = stringCurrentPositionAndDirection[2];


      private static void SetCommand()

      command = inputByLines[expectedLineWithCommand];


      private static bool StartPositionIsInvalid(string stringCurrentPositionAndDirection)


      private static bool PlateauDimensionsAreInvalid(string stringPlateauDimenstions)




      Tests around MarsRoverNavigator:



      [TestFixture]
      public class MarsRoverNavigatorShould

      [TestCase("5 5n0 0 NnL", "0 0 W")]
      [TestCase("5 5n0 0 NnR", "0 0 E")]
      [TestCase("5 5n0 0 WnL", "0 0 S")]
      [TestCase("5 5n0 0 WnR", "0 0 N")]
      [TestCase("5 5n0 0 SnL", "0 0 E")]
      [TestCase("5 5n0 0 SnR", "0 0 W")]
      [TestCase("5 5n0 0 EnL", "0 0 N")]
      [TestCase("5 5n0 0 EnR", "0 0 S")]
      [TestCase("5 5n1 1 NnM", "1 2 N")]
      [TestCase("5 5n1 1 WnM", "0 1 W")]
      [TestCase("5 5n1 1 SnM", "1 0 S")]
      [TestCase("5 5n1 1 EnM", "2 1 E")]
      public void UpdateDirectionWhenPassSpinDirections(string input, string expectedDirection)

      var marsRover = new MarsRover(input);
      marsRover.Initialize();
      marsRover.Navigate();

      var actualResult = marsRover.FinalPosition;

      actualResult.Should().BeEquivalentTo(expectedDirection);


      [TestCase("5 5n0 0 NnM", "0 1 N")]
      [TestCase("5 5n1 1 NnMLMR", "0 2 N")]
      [TestCase("5 5n1 1 WnMLMLMLM", "1 1 N")]
      [TestCase("5 5n0 0 NnMMMMM", "0 5 N")]
      [TestCase("5 5n0 0 EnMMMMM", "5 0 E")]
      [TestCase("5 5n0 0 NnRMLMRMLMRMLMRMLM", "4 4 N")]
      public void UpdatePositionWhenPassCorrectInput(string input, string expectedPosition)

      var marsRover = new MarsRover(input);
      marsRover.Initialize();
      marsRover.Navigate();

      var actualResult = marsRover.FinalPosition;

      actualResult.Should().BeEquivalentTo(expectedPosition);


      [TestCase("1 1n0 0 NnMM")]
      [TestCase("1 1n0 0 EnMM")]
      public void ReturnExceptionWhenCommandSendsRoverOutOfPlateau(string input)

      var marsRover = new MarsRover(input);
      marsRover.Initialize();

      marsRover.Invoking(y => y.Navigate())
      .Should().Throw<InvalidCommandException>()
      .WithMessage("Command is invalid: Rover is sent outside the Plateau");




      Tests around input:



      [TestFixture]
      public class MarsRoverShould

      [TestCase("5 5n0 0 NnM", 5, 5, 0, 0, "N", "M")]
      [TestCase("10 10n5 9 EnLMLMLM", 10, 10, 5, 9, "E", "LMLMLM")]
      public void ParseAnInputCorrectly(string input, int expectedXPlateauDimension, int expectedYPlateauDimension,
      int expectedXStartPosition, int expectedYStartPosition, string expectedDirection, string expectedCommand)

      var expectedPlateausDimensions = new Coordinates() X = expectedXPlateauDimension, Y = expectedYPlateauDimension ;
      var expectedStartingPosition = new Coordinates() X = expectedXStartPosition, Y = expectedYStartPosition ;

      var expectedNavigationParameters = new NavigationParameters(expectedDirection, expectedPlateausDimensions,
      expectedStartingPosition, expectedCommand);

      var marsRover = new MarsRover(input);
      marsRover.Initialize();
      var actualResult = marsRover.NavigationParameters;

      actualResult.Should().BeEquivalentTo(expectedNavigationParameters);


      [TestCase("10 10 5n1 9 EnLMLMLM")]
      [TestCase("10n5 9 EnLMLMLM")]
      [TestCase("10 An5 9 EnLMLMLM")]
      public void ReturnExceptionWhenWrongPlateauDimensionsInput(string input)

      var marsRover = new MarsRover(input);

      marsRover.Invoking(y => y.Initialize())
      .Should().Throw<IncorrectPlateauDimensionsException>()
      .WithMessage("Plateau dimensions should contain two int parameters: x and y");


      [TestCase("1 1n1 1nLMLMLM")]
      [TestCase("1 1n1 NnLMLMLM")]
      [TestCase("1 1n1nLMLMLM")]
      [TestCase("5 5n5 A NnLMLMLM")]
      [TestCase("5 5n5 1 AnLMLMLM")]
      [TestCase("1 1n5 1 NnLMLMLM")]
      public void ReturnExceptionWhenWrongStartPositionInput(string input)

      var marsRover = new MarsRover(input);

      marsRover.Invoking(y => y.Initialize())
      .Should().Throw<IncorrectStartPositionException>()
      .WithMessage("Start position and direction should contain three parameters: int x, int y and direction (N, S, W or E)");


      [TestCase("10 10; 5 9; LMLMLM")]
      [TestCase("10 10nLMLMLM")]
      public void ReturnExceptionWhenWrongInputFormat(string input)

      var marsRover = new MarsRover(input);

      marsRover.Invoking(y => y.Initialize())
      .Should().Throw<IncorrectInputFormatException>()
      .WithMessage("Error occured while splitting the input: format is incorrect");










      share|improve this question












      share|improve this question




      share|improve this question








      edited Apr 13 at 16:26









      t3chb0t

      32k54195




      32k54195









      asked Apr 13 at 16:10









      Yuliya

      362




      362

























          active

          oldest

          votes











          Your Answer




          StackExchange.ifUsing("editor", function ()
          return StackExchange.using("mathjaxEditing", function ()
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          );
          );
          , "mathjax-editing");

          StackExchange.ifUsing("editor", function ()
          StackExchange.using("externalEditor", function ()
          StackExchange.using("snippets", function ()
          StackExchange.snippets.init();
          );
          );
          , "code-snippets");

          StackExchange.ready(function()
          var channelOptions =
          tags: "".split(" "),
          id: "196"
          ;
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function()
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled)
          StackExchange.using("snippets", function()
          createEditor();
          );

          else
          createEditor();

          );

          function createEditor()
          StackExchange.prepareEditor(
          heartbeatType: 'answer',
          convertImagesToLinks: false,
          noModals: false,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          );



          );








           

          draft saved


          draft discarded


















          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f191991%2fmars-rover-kata-using-tdd-and-solid%23new-answer', 'question_page');

          );

          Post as a guest



































          active

          oldest

          votes













          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes










           

          draft saved


          draft discarded


























           


          draft saved


          draft discarded














          StackExchange.ready(
          function ()
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f191991%2fmars-rover-kata-using-tdd-and-solid%23new-answer', 'question_page');

          );

          Post as a guest













































































          Popular posts from this blog

          Greedy Best First Search implementation in Rust

          Function to Return a JSON Like Objects Using VBA Collections and Arrays

          C++11 CLH Lock Implementation