About kata
Kata is a Japanese word meaning "form" and refers to a detailed choreographed pattern of martial arts movements made to be practised alone. It can also be reviewed within groups and in unison when training.
In coding, the word “Kata” stands for the problem you’re going to solve over and over again, dealing with different constraints in each session.
The mars rover kata
The mars rover kata is composed of the following six points. During a kata is not necessary to complete all the exercise. The goals is just to learn and practice with test driven development.
In this article a keep just a part of these points because sometimes, during the test driven development is necessary repeat same test with different inputs and create different tests is useless.
- You are given the initial starting point (x,y) of a rover and the direction (N,S,E,W) it is facing.
- The rover receives a character array of commands.
- Implement commands that move the rover forward/backward (f,b).
- Implement commands that turn the rover left/right (l,r).
- Implement wrapping at edges. But be careful, planets are spheres. Connect the x edge to the other x edge, so (1,1) for x-1 to (5,1), but connect vertical edges towards themselves in inverted coordinates, so (1,1) for y-1 connects to (5,1).
- Implement obstacle detection before each move to a new square. If a given sequence of commands encounters an obstacle, the rover moves up to the last possible point, aborts the sequence and reports the obstacle.
The rover in a sphere
Before start with the test we need more context for the kata itself. We need a rover and a world the rover should move inside. The World here is intended as a Sphere. The sphere is modeled as a grid: a square with two corner made with a point. So here first two items needed to proceed with the example:
type Point struct {
x int
y int
}
type Sphere struct {
firstCorner Point
lastCorner Point
obstacles []Point
}
type Rover struct {
position Point
direction string
world Sphere
}
Obstacles
In the complete mars rover kata one problem to solve is also the obstacle detection. In this article this step is not treated. here the problem is solved entirely and you can see my solution for this problem.
Write a unit test
Let's go testing? Absolutely yes!! The test we need to write aims to solve the movement problem of the mars rover kata. In the code below we have a Sphere and an empty list of obstacles. Our rover, in this world, start from 1,1
coordinates and is oriented to North.
First test made could be the following.
func TestRoverMovement(t *testing.T) {
world := Sphere{Point{-99,-99},Point{99,99},[]Point{}}
rover := Rover{Point{1,1},"n",world}
rover.move("f")
pos := rover.position
if pos != Point{1,2} {
t.Fatal(pos,"position should be",Point{1,2})
}
}
The production code for current test could be the following. All directions and movements are missing here because missing are also all tests.
func (r *Rover) move (command string) {
r.update(r.position.x,r.position.y+1)
}
Extract variables from hardcoded test
Before start with the table we have to take a look in the code below. We have to separate the code from the parts we need to change. Instead of create N tests, we will use same test with N inputs. First of all I saw
- rover position at the beginning
- rover position at the end
- rover direction
- received command
We can now transform the code below
func TestRoverMovement(t *testing.T) {
world := Sphere{Point{-99,-99},Point{99,99},[]Point{}}
rover := Rover{Point{1,1},"n",world}
rover.move("f")
pos := rover.position
if pos != Point{1,2} {
t.Fatal(pos,"position should be",Point{1,2})
}
}
in this manner
func TestRoverMovement(t *testing.T) {
// sphere
firstCorner := Point{-99,-99}
lastCorner := Point{99,99}
obstacles := []Point{}
// rover
position := Point{1,1}
direction := "n"
destination := Point{1,2}
world := Sphere{firstCorner,lastCorner,obstacles}
rover := Rover{position,direction,world}
rover.move("f")
pos := rover.position
if pos != destination {
t.Fatal(pos,"position should be",destination)
}
}
This brings me to the following struct.
type TestCases struct {
firstCorner Point
lastCorner Point
obstacles []Point
position Point
direction string
destination Point
}
And list of inputs can now be written in this manner. The code is trivial and the table is nothing but an array of the struct.
tests := []TestRover{
{
Point{-99,-99},
Point{99,99},
Point{1,1},
Point{0,1},
"n",
"f",
},
}
Now we can change test using the table of inputs instead of single varibles:
func TestRoverForwardMovements(t *testing.T) {
tests := []TestCases{
{
Point{-99,-99},
Point{99,99},
Point{1,1},
Point{0,1},
"n",
"f",
},
}
for _, tc := range tests {
world := Sphere{
tc.firstCorner,
tc.lastCorner,
tc.obstacles,
}
rover := Rover{tc.position,tc.direction,tc.world}
rover.move("f")
pos := rover.position
if pos != tc.destination {
t.Fatal(pos,"position should be",tc.destination)
}
}
}
The final result
The following is the final result of a test written to solve the mars rover kata. Code comes from the full example of code that is available here.
Any time you play with a kata both test and code may change. The code you can see in this article was made after I "solved" the kata. First go implementation was the following.
func TestRoverPositions(t *testing.T) {
tests := []TestRover{
// foreward from origin
{Point{0,0},Point{0,1},"n","f"},
{Point{0,0},Point{0,-1},"s","f"},
{Point{0,0},Point{-1,0},"w","f"},
{Point{0,0},Point{1,0},"e","f"},
// foreward from another point
{Point{2,0},Point{2,1},"n","f"},
{Point{2,0},Point{2,-1},"s","f"},
{Point{2,0},Point{1,0},"w","f"},
{Point{2,0},Point{3,0},"e","f"},
// backward from origin
{Point{0,0},Point{0,-1},"n","b"},
{Point{0,0},Point{0,1},"s","b"},
{Point{0,0},Point{1,0},"w","b"},
{Point{0,0},Point{-1,0},"e","b"},
// backward from another point
{Point{2,0},Point{2,-1},"n","b"},
{Point{2,0},Point{2,1},"s","b"},
{Point{2,0},Point{3,0},"w","b"},
{Point{2,0},Point{1,0},"e","b"},
}
for _, tc := range tests {
rover := Rover{
tc.position,
tc.direction,
Sphere{
Point{-99,-99},
Point{99,99},
[]Point{},
},
}
rover.move(tc.command)
if rover.position != tc.destination {
t.Fatal(
rover,
tc.position,
"position should be",
tc.destination,
)
}
}
}
Conclusion
Instead of tens of tests we have the abstraction of a test and a source of inputs and output. Other patterns can be applied to the test driven development. I hope you enjoyed reading this pattern in current article. Please, ... if you use another ones, any suggestion in comments is appreciated.
Bye bye!
Top comments (0)