Test Driven Development (aka TDD)
It’s challenging to test production-grade code. Sometimes, working on features can consume almost all of your time. Furthermore, even if all tests pass and you have 100% coverage, you might not be convinced that the new feature will function well in actual use.
Using the use of Test-Driven Development, this article will walk you through creating an application (TDD). We’ll discuss what to test and how to do it. Pydantic will check data and let us run fewer tests, while Flask will give our customers access to an interface through a RESTful API. Pytest will be used for testing. By the time you’re done, you’ll have a reliable pattern that you can apply to any Python project, giving you the assurance that passing tests truly translate into functional software.
Following this procedure guarantees that the code you develop is carefully planned in order to pass these tests. This also eliminates the potential of writing tests being postponed to a later date since they may not be judged necessary in comparison to other features that may be generated during that period.
Here is a flow of tests, such as the Serial Number test.
- Writing Failing Test
- Run and Fail the Test
- Write Code to pass
- Run and Pass Tests
- Refactor
To start developing tests in Python, we’ll utilize the unittest module that comes with the language. To do this, we will create a new file called mytests.py that will include all of our tests.
Let’s start with the standard method:
Example: This code is in the file CodeForTest.py, which is in the same directory as the main code.
def standard_func():
pass
Example: This is the main code.
import unittest
from CodeForTest import *
class MyFirstTests(unittest.TestCase):
def test_standard(self):
self.assertEqual(test_standard(), 'standard_func')
if __name__ == '__main__':
unittest.main()
Output:
E
======================================================================
ERROR: test_standard (__main__.MyFirstTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Desktop/Python Test/Enablegeek.py", line 9, in test_standard
self.assertEqual(test_standard(), 'standard_func')
NameError: name 'test_standard' is not defined
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (errors=1)
This plainly shows that the test failed, as predicted. Fortunately, we have previously developed the tests, so we know it will always be available to examine this function, giving us confidence in detecting possible defects in the future.
Example:
import unittest
from CodeForTest import *
class MyFirstTests(unittest.TestCase):
def test_standard(self):
self.assertEqual(standard_func(), 81)
# self.assertEqual(standard_func(), 387420498)
if __name__ == '__main__':
unittest.main()
Output:
F
======================================================================
FAIL: test_standard (__main__.MyFirstTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Desktop/Python/Python Test/Enablegeek.py", line 9, in test_standard
self.assertEqual(standard_func(), 81)
AssertionError: 387420498 != 81
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (failures=1)
Congrats! You’ve just finished your first test. Let’s move on to a somewhat more challenging task. In Python, we’ll write a method that allows us to generate a custom numeric list comprehension.
Let’s start by building a test for a function that generates a list of a certain length.
Here is another example of the previous code.
Example:
import unittest
from CodeForTest import *
class MyFirstTests(unittest.TestCase):
def test_standard(self):
# self.assertEqual(standard_func(), 81)
self.assertEqual(standard_func(), 387420498)
if __name__ == '__main__':
unittest.main()
Output:
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Let’s start by building a test for a new function that incorporates the custom function into the list comprehension. As is customary, let us begin by writing the test. Just to be sure, we’ve included two separate cases: a function that generates a list of a certain length
Example:
import unittest
from CodeForTest import *
class MyFirstTests(unittest.TestCase):
def test_hello(self):
self.assertEqual(standard_func(), 387420498)
def test_custom_num_list(self):
self.assertEqual(len(list_func(10)), 10)
def test_custom_func_x(self):
self.assertEqual(factor_func(3,2,6), 54)
if __name__ == '__main__':
unittest.main()
Output:
.E.
======================================================================
ERROR: test_custom_num_list (__main__.MyFirstTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/tahuruzzoha/Desktop/Problem-Solving/Python/Python Test/Enablegeek.py", line 10, in test_custom_num_list
self.assertEqual(len(list_func(10)), 10)
TypeError: list_func() takes 0 positional arguments but 1 was given
----------------------------------------------------------------------
Ran 3 tests in 0.001s
TDD (Test Driven Development) is an excellent software development methodology. TDD is just the writing of tests prior to the addition of a feature in code. This technique is founded on the notion that little codes should be written rather than lengthy ones. When using TDD, every time we want to add new functionality to our code, we must first write a test for it. Following that, we add additional functionality by combining tiny code lines and testing them with our test. This strategy assists us in lowering the likelihood of serious production difficulties.
Benefits of TDD
The question now is why should one use the TDD technique. There are several advantages to using TDD. Some of the advantages are as follows:
- TDD requires us to write tests before adding new features, which implies that our whole code is covered by the tests. That is a significant advantage of TDD versus code with no test coverage.
- Before adding new functionality, TDD practitioners should have a clear goal in mind. This means that before implementing any new feature, one should be certain of the end result.
- One technique is dependent on the other in an application. When we create tests before methods, we should have clear ideas about how the methods interact with one another. This allows us to effectively integrate our approach with the entire program while also assisting in the modularization of our application.
- Because the test covers the entire code, our final application will be less buggy. This is a significant benefit of the TDD technique.
In this tutorial, we will look at the Python test-driven development technique. The unit test module is included with the official Python interpreter. These are the primary approaches we apply for Python test-driven development.
All Methods of unittest
Method | Checks That |
assertEqual(a, b) | a == b |
assertNotEqual(a, b) | a != b |
assertTrue(x) | bool(x) is True |
assertFalse(x) | bool(x) is False |
assertIs(a, b) | a is b |
assertIsNot(a, b) | a is not b |
assertIsNone(x) | x is None |
assertIsNotNone(x) | x is not None |
assertIn(a, b) | a in b |
assertNotIn(a, b) | a not in b |
assertIsInstance(a, b) | isinstance(a, b) |
assertNotIsInstance(a, b) | not isinstance(a, b) |
The basic goal of the TDD technique is to fail first and then fix your code. So we construct the test, fail it, and then change our code to pass it. This strategy is based on the premise that we should first finalize what we need, then build the goal test for it, and then begin to attain that aim.