Introduction
Test-driven development (TDD) is an approach where you write tests before writing your actual code. The idea behind TDD is to ensure that all the units in your application are working as expected, and it helps in maintaining high quality code. In Python, we can use a testing framework like unittest
or pytest
for this purpose. unittest
is a built-in module in Python.
Table of Contents
- Introduction
- What is Test-Driven Development?
- First, write the Tests for the Calculator Class
- Second, run the Tests and implement the Test Cases
- Step 1 – Create the Module
- Step 2 – Create the Class
- Step 3 – Implement the necessary functions
- Step 4 – Check the result
- Conclusion
What is Test-Driven Development?
Test-driven development (TDD) is an agile software development process that relies on the repetition of a very short development cycle: first the developer writes an automated test case that fails, then the code is written to pass the test, and finally the tests are refactored to improve the design.
First, write the Tests for the Calculator Class
We can use the unittest
module to write tests for our future but not yet existing calculator class.
Here is an example of what methods we expect in the class. Furthermore, which outputs we expect when calling them.
import unittest
from calculator import Calculator
class TestCalculator(unittest.TestCase):
def setUp(self):
self.calc = Calculator()
def test_addition(self):
result = self.calc.add(5, 7)
self.assertEqual(result, 12)
def test_subtraction(self):
result = self.calc.subtract(10, 3)
self.assertEqual(result, 7)
def test_multiplication(self):
result = self.calc.multiply(4, 5)
self.assertEqual(result, 20)
def test_division(self):
result = self.calc.divide(16, 4)
self.assertEqual(result, 4)
if __name__ == '__main__':
unittest.main()
Second, run the Tests and implement the Test Cases
To run these tests, you would save them in a file (e.g., test_calculator.py
), and then use the command line to navigate to that directory and follow the next steps.
Step 1 – Create the Module
Run: python test_calculator.py
This will automatically discover and run all tests within your TestCalculator
class, providing you with a report of which tests passed and which failed.
Output:
Traceback (most recent call last):
File "/home/user/projects/blog/testing/test_calculator.py", line 2, in <module>
from calculator import Calculator
ModuleNotFoundError: No module named 'calculator'
This means that the module calculator
does not exist. Which is correct. That means first we create a file calculator.py
. And run the test again.
Step 2 – Create the Class
Run: python test_calculator.py
Output:
Traceback (most recent call last):
File "/home/user/projects/blog/testing/test_calculator.py", line 2, in <module>
from calculator import Calculator
ImportError: cannot import name 'Calculator' from 'calculator' (/home/user/projects/blog/testing/calculator.py)
This means that the calculator
module now exists. That is correct. But the module does not contain the class Calculator
. Now you have to insert the class Calculator
into the file calculator.py
.
class Calculator:
pass
Step 3 – Implement the necessary functions
Run: python test_calculator.py
Output:
======================================================================
ERROR: test_addition (__main__.TestCalculator.test_addition)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/user/projects/blog/testing/test_calculator.py", line 9, in test_addition
result = self.calc.add(5, 7)
^^^^^^^^^^^^^
AttributeError: 'Calculator' object has no attribute 'add'
======================================================================
ERROR: test_division (__main__.TestCalculator.test_division)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/user/projects/blog/testing/test_calculator.py", line 21, in test_division
result = self.calc.divide(16, 4)
^^^^^^^^^^^^^^^^
AttributeError: 'Calculator' object has no attribute 'divide'
======================================================================
ERROR: test_multiplication (__main__.TestCalculator.test_multiplication)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/user/projects/blog/testing/test_calculator.py", line 17, in test_multiplication
result = self.calc.multiply(4, 5)
^^^^^^^^^^^^^^^^^^
AttributeError: 'Calculator' object has no attribute 'multiply'
======================================================================
ERROR: test_subtraction (__main__.TestCalculator.test_subtraction)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/user/projects/blog/testing/test_calculator.py", line 13, in test_subtraction
result = self.calc.subtract(10, 3)
^^^^^^^^^^^^^^^^^^
AttributeError: 'Calculator' object has no attribute 'subtract'
----------------------------------------------------------------------
Ran 4 tests in 0.001s
FAILED (errors=4)
Now the class and the module are there but all tested functions are missing. Therefore all 4 tests fail. Create all functions now.
class Calculator:
def add(self, x, y):
return x + y
def subtract(self, x, y):
return x - y
def multiply(self, x, y):
return x * y
def divide(self, x, y):
if y != 0:
return x / y
else:
raise ValueError("Cannot divide by zero")
Step 4 – Check the result
The implementation now meets all the requirements of the unit test. The same procedure is used for python TDD programming. This ensures that the code has a very high test coverage.
Run: python test_calculator.py
Output:
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK
Now all requirements from the tests are covered.
Conclusion
TDD is not just about writing code that passes the tests; it’s about writing code that is easy to understand, maintain, and extend. By focusing on testing first in Python, we can ensure our code behaves as expected and helps us avoid bugs in the future. This approach also encourages a clean separation of concerns, making our code easier to read and understand.