Categories
Blog Software Development

Test Driven Development – A practical Example

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.

Leave a Reply

Your email address will not be published. Required fields are marked *