The standard unittest
Python package greatly facilitates automated testing. This package requires that we rewrite our tests to be compatible. The first test would have to be rewritten in a class
, as follows:
from bisection import bisect import unittest class TestIdentity(unittest.TestCase): def test(self): result = bisect(lambda x: x, -1.2, 1.,tol=1.e-8) expected = 0. self.assertAlmostEqual(result, expected) if __name__=='__main__': unittest.main()
Let's examine the differences to the previous implementation. First, the test is now a method and a part of a class. The class must inherit from unittest.TestCase
. The test method's name must start with test
. Note that we may now use one of the assertion tools of the unittest
package, namely assertAlmostEqual
. Finally, the tests are run using unittest.main
. We recommend to write the tests in a file separate from the code to be tested. That is why it starts with an import
. The test passes and returns as follows:
Ran 1 test in 0.002s OK
If we run it with a loose tolerance parameter, for example, 1.e-3
, a failure of the test would have been reported:
F ====================================================================== FAIL: test (__main__.TestIdentity) ---------------------------------------------------------------------- Traceback (most recent call last): File "<ipython-input-11-e44778304d6f>", line 5, in test self.assertAlmostEqual(result, expected) AssertionError: 0.00017089843750002018 != 0.0 within 7 places ---------------------------------------------------------------------- Ran 1 test in 0.004s FAILED (failures=1)
Tests can and should be grouped together as methods of a test class, as given in the following example:
import unittest from bisection import bisect class TestIdentity(unittest.TestCase): def identity_fcn(self,x): return x def test_functionality(self): result = bisect(self.identity_fcn, -1.2, 1.,tol=1.e-8) expected = 0. self.assertAlmostEqual(result, expected) def test_reverse_boundaries(self): result = bisect(self.identity_fcn, 1., -1.) expected = 0. self.assertAlmostEqual(result, expected) def test_exceeded_tolerance(self): tol=1.e-80 self.assertRaises(Exception, bisect, self.identity_fcn, -1.2, 1.,tol) if __name__=='__main__': unittest.main()
Here, in the last test we used the method unittest.TestCase.assertRaises
. It tests whether an exception is correctly raised. Its first parameter is the exception type, for example, ValueError
, Exception
, and its second argument is the name of the function, which is expected to raise the exception. The remaining arguments are the arguments for this function. The command unittest.main()
creates an instance of the TestIdentity
class and executes those methods starting with test
.
The class unittest.TestCase
provides two special methods, setUp
and tearDown
, which run before and after every call to a test method. This is needed when testing generators, which are exhausted after every test. We demonstrate this by testing a program which checks the line in a file in which a given string occurs for the first time:
class NotFoundError(Exception): pass def find_string(file, string): for i,lines in enumerate(file.readlines()): if string in lines: return i raise NotFoundError( 'String {} not found in File {}'.format(string,file.name))
We assume that this code is saved in the find_in_file.py
file. A test has to prepare a file and open it and remove it after the test as given in the following example:
import unittest import os # used for, for example, deleting files from find_in_file import find_string, NotFoundError class TestFindInFile(unittest.TestCase): def setUp(self): file = open('test_file.txt', 'w') file.write('aha') file.close() self.file = open('test_file.txt', 'r') def tearDown(self): self.file.close() os.remove(self.file.name) def test_exists(self): line_no=find_string(self.file, 'aha') self.assertEqual(line_no, 0) def test_not_exists(self): self.assertRaises(NotFoundError, find_string, self.file, 'bha') if __name__=='__main__': unittest.main()
Before each test setUp
is run and then tearDown
is executed.
18.223.107.85