The first part of this tutorial series showed how to set up a Python project and use py.test test runner for Test Driven Development (TDD). Before proceeding any further, be sure to check it out as this part will build directly on it.
In this part of the series I will focus on implementing some additional functionality to our interactive interpreter. The main goal is to show how the behavior specified in the tests helps us to shape it up. Last time we managed to implement an interpreter that could be used to perform simple calculations. It would be extremely useful if it was possible to store the results into variables and then reuse them as part of calculations. Let's focus on that next.
Getting and Setting Variables
This problem can be split in distinct parts. There has to be a way to set variable, get its value and finally use it as a part of a mathematical expression. I will focus on the last part after we have sorted out how to get/set. Let's formalize the get and set observations:
test_interpreter.py:
I separated the new tests into a new inner class named aptly as TestAccessVariable. The idea is that it encapsulates all tests related to variable access of course. Note that I set it to use the same setup method as its parent. I also added a handy alias for the interpret method (self.interpret). Here's the current implementation after this addition:
interpreter.py:
Yeah. It's pretty horrible but it works just as it should! Let's have a look at the final part of the problem. How to use the variables as a part of a mathematical expression?
Tidying Up Math Expression Tests
The current code to test math expressions doesn't look that good. It's probably better to convert those to use an inner class of their own and have some sort of table based solution to provide expressions and the results matching to them. Here's the implementation:
test_interpreter.py:
As you can see I added a new utility module to the system. It contains Operations helper class that encapsulates the functionality needed by the testing logic. Here's the current implementation:
utils.py:
It looks a little bit nicer now. Perhaps the loop in test_operations should belong to Operations even so you could do something like self.operations.test(self.interpret) but that's for later. It's still missing the functionality we were originally after, though.
Tidying Up and Variables in Expressions
As "TestMath.setup_method = setup_method" kind of syntax doesn't look particularly nice, it's probably better the setup_method issue by subclassing. Let's do that and add the tests for variables in expressions. Here's the code as a whole as I had to do quite a few changes structurally:
test_interpreter.py:
And here's the code that implements this behavior:
interpreter.py:
As you can see the new tests forced me to abstract out a class for variables. Otherwise the code is pretty much the same as before.
Missing Cases of Behavior
There are still some blind spots in the code. What if we want to assign an expression to a variable? Or what if we want to assign a value of a variable to another variable? Let's solve these questions next. As always, let's write tests to cover these cases:
test_interpreter.py:
As expected, the tests fail. Let's implement the code needed:
interpreter.py:
Non-existent Variables to null and Refactoring
It's starting to look a bit ugly. Fortunately we can try to refactor it. One possible way to do this would be to change the definition of variables so that if one is not found, it returns some sort of null value. Then it would be possible to treat interpret method recursively so that r_value could be parsed using it. This should tidy up the duplication. Let's try that next:
test_interpreter.py:
I think that looks like an acceptable behavior. Let's change the implementation as well:
interpreter.py:
Interestingly Variables class became redundant so I got rid of it. Note also the simplified logic.
Summary
After a few detours we managed to restructure the tests in a bit more sensible form. We also managed to make it possible to assign variables, get their values and use them mathematical expressions. There is still much left to do.
You may find the source code of this part of the series here. The next part of the series is available here.