The crucial difference between TDD and traditional testing is the moment in which we are writing the tests. When we are writing code using TDD we first write the tests and then the code itself, and not another way around. Another important difference is that we are writing small chunks of code to satisfy our test. This way the process itself drives our design and forces us to keep things simple.
1. The benefit of this approach is that we are minimizing the possibility of forgetting to write tests for some part of the code.
2. Ideally, we end up with the code that is fully tested upfront and solutions that are implemented using TDD usually have 90%-100% of code covered with tests.
So, by using TDD we avoid creating over complicated designs and overengineered systems. Arguably this is the biggest benefit of this approach.
BEST Python Testing Frameworks
doctest [Another Link]
unittest
pytest
Nose
robotframework with selenium
Testify
Sample Code:
import unittest
from rick import Rick
class RickTests(unittest.TestCase):
def test_universe(self):
rick = Rick(111)
self.assertEqual(rick.universe, 111)
if __name__ == '__main__':
unittest.main()
Of course, if we run this test we will get an error saying that Rick class is not existing:
ERROR: test_universe (main.RickTests)
Traceback (most recent call last):
File "rick_tests.py", line 5, in test_universe
rick = Rick(111)
NameError: name 'Rick' is not defined
Ran 1 test in 0.001s
FAILED (errors=1)
We need to define the class and initialize it through the constructor with the value for the universe:
class Rick(object):
def __init__(self, universe):
self.universe = universe
Now, when we re-run the tests, we get this:
test_universe (main.RickTests) … ok
--------------------------------------------------
Ran 1 test in 0.000s
OK
What are Mock Objects:
Well, in Object-Oriented programming mock objects are defined as simulated objects. They mimic the behavior of real objects in controlled ways.
Why do we need Mock Objects:
Well, the whole point of unit testing is isolating certain functionality (unit) and test just that. We are trying to remove all other dependencies.
What are other dependencies: can be a database or file system
1. we need to take care of the data in the database before and after every test, and that is something we want to avoid.
2. In some Other times dependency can be just some other class or function.
A mock object should be used in the situations when:
The real object is slow
The real object rarely and is difficult to produce artificially
The real object produces non-deterministic results
The real object does not yet exist (often the case in TDD )
Most useful one with Mock Objects and Mock Classes is this Library.
Let’s begin with the usual “hello world”:
import unittest
from mycode import *
class MyFirstTests(unittest.TestCase):
def test_hello(self):
self.assertEqual(hello_world(), 'hello world')
Notice that we are importing helloworld() function from mycode file. In the file mycode.py we will initially just include the code below, which creates the function but doesn’t return anything at this stage:
def hello_world():
pass
Running python mytests.py will generate the following output in the command line:
F
====================================================================
FAIL: test_hello (__main__.MyFirstTests)
--------------------------------------------------------------------
Traceback (most recent call last):
File "mytests.py", line 7, in test_hello
self.assertEqual(hello_world(), 'hello world')
AssertionError: None != 'hello world'
--------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (failures=1)
This clearly indicates that the test failed, which was expected. Fortunately, we have already written the tests, so we know that it will always be there to check this function, which gives us confidence in spotting potential bugs in the future.
To ensure the code passes, lets change mycode.py to the following:
def hello_world():
return 'hello world'
Running python mytests.py again we get the following output in the command line:
.
--------------------------------------------------------------------
Ran 1 test in 0.000s
OK
In the file mytests.py this would be a method test_custom_num_list:
import unittest
from mycode import *
class MyFirstTests(unittest.TestCase):
def test_hello(self):
self.assertEqual(hello_world(), 'hello world')
def test_custom_num_list(self):
self.assertEqual(len(create_num_list(10)), 10)
This would test that the function create_num_list returns a list of length 10. Let’s create function create_num_list in mycode.py:
def hello_world():
return 'hello world'
def create_num_list(length):
pass
Running python mytests.py will generate the following output in the command line:
E.
====================================================================
ERROR: test_custom_num_list (__main__.MyFirstTests)
--------------------------------------------------------------------
Traceback (most recent call last):
File "mytests.py", line 14, in test_custom_num_list
self.assertEqual(len(create_num_list(10)), 10)
TypeError: object of type 'NoneType' has no len()
--------------------------------------------------------------------
Ran 2 tests in 0.000s
FAILED (errors=1)
This is as expected, so let’s go ahead and change function create_num_list in mytest.py in order to pass the test:
def hello_world():
return 'hello world'
def create_num_list(length):
return [x for x in range(length)]
Executing python mytests.py on the command line demonstrates that the second test has also now passed:
..
--------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
For some more examples on how TDD can be implemented, refer here.
Suggested Links:
freecodecamp
rubikscode