Mars Rover Kata using TDD and SOLID
Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
7
down vote
favorite
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
andY
, 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");
c# programming-challenge unit-testing
add a comment |Â
up vote
7
down vote
favorite
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
andY
, 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");
c# programming-challenge unit-testing
add a comment |Â
up vote
7
down vote
favorite
up vote
7
down vote
favorite
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
andY
, 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");
c# programming-challenge unit-testing
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
andY
, 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");
c# programming-challenge unit-testing
edited Apr 13 at 16:26
t3chb0t
32k54195
32k54195
asked Apr 13 at 16:10
Yuliya
362
362
add a comment |Â
add a comment |Â
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
active
oldest
votes
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
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
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password