Step Seven
Let’s look at the rest of our list:
- Addition of multiple numbers
- Multiplication
- Division
- Bracketed Expressions
In the next step, we are going to develop multiplication and division. These two functionalities can be done at the same step, as the necessary changes are very similar and we have the whole test infrastructure in place.
We begin with the integration tests. Let’s add some examples for multiplication and division. There is also a special case for division — the division by zero. In this case, an error message shall be shown.
def test_multiplication_of_two_numbers(self): output = self.app.calculate("3 * 5") self.assertEqual(output, "15") output = self.app.calculate("0 * 5") self.assertEqual(output, "0") output = self.app.calculate("1.5 * -1") self.assertEqual(output, "-1.50") def test_division_of_two_numbers(self): output = self.app.calculate("3 / 5") self.assertEqual(output, "0.60") output = self.app.calculate("0 / 5") self.assertEqual(output, "0.00") output = self.app.calculate("1.5 / -1") self.assertEqual(output, "-1.50") def test_division_by_zero(self): output = self.app.calculate("5 / 0") self.assertEqual(output, "Division By Zero")
Calculation
All the new tests are failing. The error messages show that the result of the calculation is always 0 instead of the expected value.
FAIL: test_multiplication_of_two_numbers (testIntegrationCalculatorApplication.TestIntegrationCalculatorApplication) ---------------------------------------------------------------------- Traceback (most recent call last): File ".../testIntegrationCalculatorApplication.py", line 96, in test_multiplication_of_two_numbers self.assertEqual(output, "15") AssertionError: '0' != '15' - 0 + 15
Why would the result be zero instead of “Invalid Expression”? This is because the Tokenizer already understands the ‘/’ and ‘*’ operators, which we added in Step Three. But the Calculator does not use these operators. That’s why it returns the default result. The default is zero, which explains the output of the test. This shows us the next step we have to take: We need to implement division and multiplication in the class Calculator. First, we write the tests:
def test_multiplication_of_two_numbers(self): result = self.calculator.calculate([Number(3), Operator.Multiplication, Number(5)]) self.assertEqual(result, 15) result = self.calculator.calculate([Number(120), Operator.Multiplication, Number(80)]) self.assertEqual(result, 9600) def test_division_of_two_numbers(self): result = self.calculator.calculate([Number(3), Operator.Division, Number(5)]) self.assertEqual(result, 0.6) result = self.calculator.calculate([Number(120), Operator.Division, Number(80)]) self.assertEqual(result, 1.5) def test_division_by_zero(self): with(self.assertRaises(ZeroDivisionError)): result = self.calculator.calculate([Number(3), Operator.Division, Number(0)])
In case of a division by zero, the calculator should throw an exception. We can use one of Python’s built-in exceptions: ZeroDivisionError. This exception is thrown by the Python runtime system when a division by zero occurs, so we don’t have to throw it ourselves. This makes the extensions to Calculator straightforward:
class Calculator: def calculate(self, tokens): if(len(tokens) == 3): operator = tokens[1] if operator == Operator.Addition: sum = tokens[0].get_value() + tokens[2].get_value() return sum if operator == Operator.Subtraction: sub = tokens[0].get_value() - tokens[2].get_value() return sub if operator == Operator.Multiplication: mul = tokens[0].get_value() * tokens[2].get_value() return mul if operator == Operator.Division: div = tokens[0].get_value() / tokens[2].get_value() return div return 0
Catching the Exceptions
This fixes all the tests, except of one integration test:
====================================================================== ERROR: test_division_by_zero (testIntegrationCalculatorApplication.TestIntegrationCalculatorApplication) ---------------------------------------------------------------------- Traceback (most recent call last): File ".../testIntegrationCalculatorApplication.py", line 115, in test_division_by_zero output = self.app.calculate("5 / 0") File ".../CalculatorApplication.py", line 18, in calculate result = self.calculator.calculate(tokens) File ".../Calculator.py", line 18, in calculate div = tokens[0].get_value() / tokens[2].get_value() ZeroDivisionError: division by zero
The ZeroDivisionError is thrown in this test, but it is not caught. This is the reason why the test fails. We need to catch the exception in CalculatorApplication and show the corresponding error message in this case:
def test_zero_division_exception(self): self.calculator.calculate.side_effect = ZeroDivisionError result = self.app.calculate("") self.assertEqual(result, "Division By Zero")
To make this test work, we change this line in CalculatorApplication.calculate
result = self.calculator.calculate(tokens)
to the following implementation:
try: result = self.calculator.calculate(tokens) except(ZeroDivisionError): return "Division By Zero" except: return "Error during Calculation"
This makes all tests pass. We check for duplication but find none. We can mark this step as finished.
Summary of Step Seven
We have implemented multiplication and division in a single step. As the structure of our program was well suited for the addition of new operators, we could do this easily.
Now all the basic operations between two numbers are implemented. In the following steps, we will implement expressions with three or more numbers.