DISCLAIMER: This is not a tutorial on how to learn Python, in fact, I have never used Python before so don’t take me as example. What I want to share with you is how to learn a new language, strenghen the TDD principles and practice a Kata in an easy way.
Last week I attended the Global day of Code retreat in Barcelona. It was a wonderful experience that generated me a lot of energy to code again (I became a manager some time ago and I don’t code as often as I’d like to). That day we used the Conway’s Game of Life which I recommend to practice the TDD approach.
After investigating about several other katas I stumbled upon the Cyber dojo online tool. Basically this is a web page in which you can practice any kata and choose among more than 20 languages you want to learn without the need to do any setup at all. In other words, if you want to grasp the basics for a new language, this is the perfect place.
Also, one of the nice features from Cyber dojo is that after you’re done with all the coding you wanted to do, you can download all your changes into a zipped Git file and then you have the repository with all the changes that you worked on.
I wanted to give it a try and decided to go for python. My background is mainly Java and I expected python to be easy to learn.
These are the steps I followed to learn about Python while continue practicing TDD:
- Find an interesting kata (Mars Rover) and create it in Cyber Dojo.
- Make the first test pass
- Start learning python: Create the classes rover.py and test_rover.py
- Iteratively, create a new test in test_rover.py and do the implementation in rover.py
- Verify that the kata has been implemented properly
- Learn other features from Cyber dojo
- My conclusions
Find an interesting kata (Mars Rover) and create it in Cyber Dojo
I decided to go for one kata called Mars Rover because I thought it would be easy enough to learn a new language and at the same time challenging enough.
So, first of all, I connected to Cyber Dojo and created one new Kata by clicking the button Setup and then choosing the language Python and the exercise Verbal (this exercise, unlike others, starts with an empty instructions file that you can change by entering your own instructions).
After clicking OK, this would give me a new ID that identifies all the Kata that I’ll be working on. Then clicked start.
By default, Cyber Dojo starts with these files:
- cyber-dojo.sh: main sh to call python
- output: file containing the result of the execution
- test_untitled.py: by default, it starts with a test file to check the implementation. Great for our TDD purposes
- test.py: will contain the implementation of the kata. I’ll rename it to something more meaningful
- instructions: the instructions file that contains the requirements of the kata.
So, the first thing is to paste the instructions from the Kata in the instructions file:
– Develop an api that moves a rover around a grid.
– 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).
– The only commands you can give the rover are f,b,l, and r.
– Implement wrapping from one edge of the grid to another. (planets are spheres after all)
– 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 and reports the obstacle.
Here is an example:
– Let’s say that the rover is located at 0,0 facing North on a 100×100 grid.
– Given the command “ffrff” would put the rover at 2,2 facing East.
Making the first test pass
If we check the output file, we’ll see that the test fails. Basically the Cyber Dojo platform always starts with a failing test. My first task is to fix it by changing the return value from the implementation class (untitled.py) to match the value expected in the assert (in test_untitled.py).
class Untitled: def answer(self): return 54 # By changing it from the original 42 to 54 the test passes.
Start learning python: Create the classes rover.py and test_rover.py
Now, this is where I start learning python… but this time, I won’t be using any particular tutorial… I just want to experience with it a bit and what helps me the most is trying to find solutions for the challenges that I’ll be facing with the Kata.
Once the first test passed, I focus on creating my own classes rover.py and test_rover.py (taking the existing untitled ones as a reference) to start implement the kata.
test_rover.py contains a class in python called TestRover that extends from unittest.TestCase (same as test_untitled.py) and it also imports rover package to use it:
import rover import unittest class TestRover(unittest.TestCase): if __name__ == '__main__': unittest.main()
class Rover: def answer(self): return 54
Iteratively, create a new test in test_rover.py and do the implementation in rover.py
By default, I’ll follow these steps:
- Always practice following the TDD approach. First test, then implementation, finally refactor.
- Once the test fails, I’ll work on the implementation until I make it pass
- Because I don’t know python, every time I don’t know how to do something, I’ll google looking for a solution for that particular challenge
- Implement the solution and adapt it to my needs
- Write the next test, etc…
What I’ll do next, is write tests for all the requirements following the previous steps. Every time I write a test, I’ll stop and modify the implementation to make the test pass. I won’t add the details for all the kata, but you can find it at the end of the exercise.
In here I’ll focus mainly on the challenges that I found in Python:
- Class constructors and local variables
- Unit tests and asserts
- Conditionals and for loops
- Arrays and Array of arrays
- New classes
- Multiple constructors or constructors with different parameters
Class constructors and local variables
In Python, a class constructor is the method that is named as __init__ and can accept several parameters. In order to define the Rover class and the parameters needed for it I decided to create this constructor. We can assign those parameters to object variables, but the variables will be accessible from outside the class as well, so there’s no need for getters and setters.
class Rover: def __init__(self, x, y, orientation): self.x = x self.y = y self.orientation = orientation
Unit tests and asserts
Cyber dojo already comes by default with some testing framework for Python which is ‘unittest’. In order to make it work you just need to import the package unittest and make a class extend the class TestCase from that package.
You can use several asserts to leverage unittest and most of them you can find them in the unittest link. For the Kata I started using assertEquals and moved to assertTrue or others as I was learning them. Basically each test is a method whose name start with the letters ‘test’. Here you have a couple of examples of tests that I created for the kata.
import rover import unittest class TestRover(unittest.TestCase): # You are given the initial starting point (x,y) of a rover # and the direction (N,S,E,W) it is facing. def testWhenTheRoverIsCreatedThenTheVariablesCanBeAccessed(self): r = rover.Rover(10,12,'N') self.assertEqual(10, r.x) self.assertEqual(12, r.y) self.assertEqual('N', r.orientation) # Implement commands that move the rover forward/backward (f,b). def testTheRoverFacesNorthAndReceivesAForwardCommand(self): r = rover.Rover(5,10,'N') r.executeCommands(['f']) self.assertEqual(5,r.x) self.assertEqual(11,r.y)
Practicing with the kata I also had to learn how to raise, capture exceptions and read the exception message to compare it with the expected message value. In python, exceptions are raised easily, you just need to call raise Exception(message) with the message you want the exception to have like: raise Exception(‘Incorrect position’).
In the test case, you need to capture the exception using try: … except and then read the exception message using the function str. Here’s an example:
def testPositionIsNegative1ThenRaiseException(self): try: r = rover.Rover(-10,12,'X') except Exception as exc: self.assertEqual('Incorrect position',str(exc))
Conditionals and for loops
In the kata, there was a need for the Rover to act according to a set of commands (forward, backward, left or right) represented with their initials (f,b,l,r) which required learning how python was using the conditional clauses. Because there were several commands passed to the Rover in a char sequence, there is also a need to learn any loop to execute each of them. This is one example of conditionals inside a loop:
def executeCommands(self, commands): for idx, val in enumerate(commands): if self.orientation == 'N': if val=='f': self.y += 1 if val=='b': self.y -= 1 if val=='r': self.orientation = 'E' if val=='l': self.orientation = 'W' elif self.orientation == 'E': if val=='f': self.x += 1 if val=='b': self.x -= 1 if val=='r': self.orientation = 'S' if val=='l': self.orientation = 'N' elif self.orientation == 'S': if val=='f': self.y -= 1 if val=='b': self.y += 1 if val=='r': self.orientation = 'W' if val=='l': self.orientation = 'E' elif self.orientation == 'W': if val=='f': self.x -= 1 if val=='b': self.x += 1 if val=='r': self.orientation = 'N' if val=='l': self.orientation = 'S'
As you can see, python doesn’t require to close the for or the if/elif. It works by indentation.
Arrays and Array of arrays
I decided to practice a bit with arrays. There’s a requirement that says that planets are round and they have certain dimensions. Therefore one possible way to treat these dimensions is through an array of two positions. These arrays are defined by putting the values inside square brackets and then separated by commas. For example one planet with dimensions 10, 10 would be declared as: [10,10].
Later in the exercise, there was a need to define obstacles for the Rover. I assumed each obstacle would be defined as a position (e.g: [0,2]) and all the obstacles in a planet would be defined as an array of positions: [[0,2],[3,4],[5,5]].
Initially, when dealing only with dimensions, I decided to treat dimensions as an array of two positions and I was passing them to the constructor so that the Rover would act as if it would be in a planet. Later on, with the new requirement of “obstacles”, there was a need to redefine them and create one class Planet that would contain both dimensions and obstacles. This is the result:
class Planet: def __init__(self, dimensions): self.dimensions = dimensions self.obstacles =  def addObstacles(self, obstacles): for idx, val in enumerate(obstacles): if (val >= self.dimensions or val >= self.dimensions): raise Exception('Error, obstacle placed out of dimensions') self.obstacles.extend(obstacles) def isThereAnObstacle(self, obstacle): return (obstacle in self.obstacles)
This class would need to be passed to the Rover for it to know on which planet it was staying.
Multiple constructors or constructors with different parameters
Finally, I decided that I wanted to pass the Planet as one parameter of the constructor, therefore I needed two constructors. One without planet and one with planet. The one without planet I needed it for all the tests that were already passing before and did not require any planet. The one with planet was needed for all the tests with dimensions and obstacles.
Unfortunately, I didn’t find that Python was accepting more than one constructor and instead was using two pointers that would contained named and unnamed parameters. I had to modify the constructor resulting in this:
class Rover: def __init__(self, *args, **kwargs): self.x = args self.y = args self.orientation = args self.planet = None if len(args) > 3: self.planet = args if [self.x,self.y] in self.planet.obstacles: raise Exception('Error, the rover cannot be placed in (' + `self.x` + ',' + `self.y` + ') as there is an obstacle there' if (self.orientation!='N' and self.orientation!='S' and self.orientation != 'E' and self.orientation != 'W'): raise Exception('Incorrect orientation') if self.x < 0 or self.y < 0: raise Exception('Incorrect position')
Verify that the kata has been implemented properly
If you followed strict TDD and you created all the required tests for each of the requirements, then you probably ended with a much larger test_rover.py than rover.py (and definitely more than planet.py). The nice thing about TDD is that now you could do any refactoring and you would have the safety net that would help you verify that the functionality is still intact.
Actually the Cyber dojo web has a wonderful tool to show you how has it been the evolution of all the tests. The traffic lights at the bottom show the results of the executions every time you clicked the test button. In TDD, a perfect sequence of traffic lights would be something like this:
Red representing a new written test that failed and green doing the implementation to make the test pass.
In my case, I had many yellows representing compile errors and definitely more reds than I’d like to… but it always helped me to see where I was and allowed me to return to the last passed state easily.
Learn other features from Cyber dojo
Cyber dojo helped me a lot to learn this new language. I cannot consider myself that I know python, but I’d probably feel comfortable reading some code after doing this exercise.
Other things that I liked from Cyber Dojo are as follow:
Diff reviews: I could easily review any change between two different revisions and check what I had changed online.
Revert: At any given time, I could revert to a previously known state.
Fork: If I wanted to try something out, I could always fork the current branch and create another one to do my tests. Cyber dojo represents this with different animals.
Download: And probably the last and more important for me, I could download all the work done and treat it offline with any Git client.
I hope you liked this new approach of learning the very basics of a new language. In my case, I realized I am more interested when I need to deal with a specific challenge rather than looking at chapters in one tutorial. It helps me a lot to visually see if my tests are passing or failing and what do I need to do to fix them. And just go to google when I need to learn something new.
It didn’t take me much time to do this exercise, probably around 3 hours, and by the end of it, I had the sense that I could start using Python to do some basic stuff. Also, the fact that those are not very long katas help me stay excited and motivated.
I encourage you to try it out yourself if you’re interested in learning any new language or probably try Cyber Dojo when you have your next katas in group. I didn’t check it much, but there’s a Dashboard that shows the evolution from all the teams.
If you are interested in how I solved this Mars Rover kata, you can download all this code directly from Cyber Dojo in this link