This article is dedicated to discuss Python documentation and test modules.
Table of content
Code
- Complete Python code for the following content can be found from my Github.
- The code is designed for solving the Teaser challenge.
Documentation
pydoc module
Write documentation
-
pydocmodule will generate aman-like documentation for Python functions. -
To write documentation just use a commented test after the function definition as shown in the following code
def dist(x1,y1, x2,y2, x3,y3): ''' compute distance from a point to line segment x3,y3 is the point perform the following test >>> dist(-7.83434151195,17.378188238,-17.5348765366,0.375603802,-0.0,0.0) 15.416540040627943 ''' px = x2-x1 py = y2-y1 something = px*px + py*py u = ((x3 - x1) * px + (y3 - y1) * py) / float(something) if u > 1: u = 1 elif u < 0: u = 0 x = x1 + u * px y = y1 + u * py dx = x - x3 dy = y - y3 dist = math.sqrt(dx*dx + dy*dy) return dist -
It also allows documentation for classes as shown in the following code
class TestMethods(unittest.TestCase): ''' class to define a set of unit test with Python unittest module ''' def test_dist(self): ''' test the function dist() ''' self.assertEqual(dist(-7.83434151195,17.378188238,-17.5348765366,0.375603802,-0.0,0.0), 15.416540040627943) def test_GPS2POS(self): ''' test GPS2POS function ''' self.assertEqual(GPS2POS((52.516288,13.377689)), (-6.48982764810209, 9.159322471000536)) pass
Generate documentation
-
When finishing writing the documentation for the delivered functions, the document of the function can be seen with the following command in the command line.
pydoc solution -
The documentation of the function will print on the screen.
Help on module solution:
NAME solution
FILE /Users/su/GitHub/tmpSolution/solution.py
DESCRIPTION Python script to solve the teaser puzzle from Zalando document for this script can be generate with $pydoc solution unit text for this script can be triggled with $python solutin.py -v
CLASSES unittest.case.TestCase(builtin.object) TestMethods
class TestMethods(unittest.case.TestCase)
| class to define a set of unit test with Python unittest module
|
| Method resolution order:
| TestMethods
| unittest.case.TestCase
| __builtin__.object
|
| Methods defined here:
|
| test_GPS2POS(self)
| test GPS2POS function
|
| test_dist(self)
| test the function dist()
|
| ----------------------------------------------------------------------
| Methods inherited from unittest.case.TestCase:
|
| __call__(self, *args, **kwds)
FUNCTIONS GPS2POS((lat, lng)) transform from GPS to coordinate system (POS) perform the following test: >>> GPS2POS((52.516288,13.377689)) (-6.48982764810209, 9.159322471000536)
POS2GPS((x, y))
transform from coordinate system (POS) to GPS
compute_joint_probability(ss, gatePOS, satellitePOS, riverPOS)
compute joint probability of all point in the search space
dist(x1, y1, x2, y2, x3, y3)
compute distance from a point to line segment
x3,y3 is the point
perform the following test
>>> dist(-7.83434151195,17.378188238,-17.5348765366,0.375603802,-0.0,0.0)
15.416540040627943
find_her()
the function is designed to output locations and probabilities
plot_res(res)
prob_gate(pointPOS, gatePOS)
compute probability according to lognormal distribution base on gate
prob_river(pointPOS, riverPOS)
compute probability according to Gaussian distribution base on river
prob_satellite(pointPOS, satellitePOS)
compute probability according to Gaussian distribution for satellite
save_probability(res)
save probability to file
show_result(res)
show results on google map
transformation(riverGPS, satelliteGPS, gateGPS, startGPS, stopGPS)
wrapper function to transform the distance from GPS to locations POS
DATA author = ‘Hongyu Su’ else = ‘other information can be documented here’ version = ‘1.0’ gateGPS = (52.516288, 13.377689) lognorm = <scipy.stats._continuous_distns.lognorm_gen object> norm = <scipy.stats._continuous_distns.norm_gen object> riverGPS = [(52.529198, 13.274099), (52.531835, 13.29234), (52.522116,… satelliteGPS = [(52.590117, 13.39915), (52.437385, 13.553989)] startGPS = (52.434011, 13.274099) stopGPS = (52.564011, 13.554099)
VERSION 1.0
AUTHOR Hongyu Su
# Unit test
This brings an old topic of test-driven development (TDD) which is to design a software/function/programme starting with writing test case. After that write the expected outcome of the test case. Finally, implement the functionalities to pass the test case.
## `doctest` module
### Build test case
- `doctest` module is a Python builtin which allows building test case into the source code, in particular documentation, of the function.
- Import the `doctest` module into your script with the following code `import doctest`.
- Each line in documentation part of the source code starting with `>>>` will run as if under the interactive Python shell, which is counted as a test case.
- The return from the test case should match exactly the following line written in the documentation part of the Python code.
- For example, the following code will compute the shortest distance of a point to a line segment, and a test case is written into the documentation part of the code.
```python
def dist(x1,y1, x2,y2, x3,y3):
'''
compute distance from a point to line segment
x3,y3 is the point
perform the following test
>>> dist(-7.83434151195,17.378188238,-17.5348765366,0.375603802,-0.0,0.0)
15.416540040627943
'''
px = x2-x1
py = y2-y1
something = px*px + py*py
u = ((x3 - x1) * px + (y3 - y1) * py) / float(something)
if u > 1:
u = 1
elif u < 0:
u = 0
x = x1 + u * px
y = y1 + u * py
dx = x - x3
dy = y - y3
dist = math.sqrt(dx*dx + dy*dy)
return dist
- In particular, this is a test case in the code which compute the distance with function
dist(-7.83434151195,17.378188238,-17.5348765366,0.375603802,-0.0,0.0)with expected return value15.416540040627943.
Run test case
-
There are two ways to run the test case
-
To enable the test, add
doctest.testmod()to the Python script. For example, in my code it looks likeif __name__ == '__main__': ''' __name__ to make sure the function be excuted when used as a script not as a loaded module ''' # doc test doctest.testmod() # unit test unittest.main() # run actual code find_her() passThen run the test with the following command
python solution.py -vThe command line argument
-vwill enable verbosity output which print detail information of the test. As mentioned already, this requires adding the abovedoctest.testmod()into your code. -
As alternative, the test can be perform with the following command
python -m doctest -v solution.pyThis is more flexible and does not need to add the above
doctest.testmod()into your code. -
The above command will generate the following information printed to the screen. Oh I have another function
GPS2POSunder test as well.
Trying: GPS2POS((52.516288,13.377689)) Expecting: (-6.48982764810209, 9.159322471000536) ok Trying: dist(-7.83434151195,17.378188238,-17.5348765366,0.375603802,-0.0,0.0) Expecting: 15.416540040627943 ok 14 items had no tests: main main.POS2GPS main.TestMethods main.TestMethods.test_GPS2POS main.TestMethods.test_dist main.compute_joint_probability main.find_her main.plot_res main.prob_gate main.prob_river main.prob_satellite main.save_probability main.show_result main.transformation 2 items passed all tests: 1 tests in main.GPS2POS 1 tests in main.dist 2 tests in 16 items. 2 passed and 0 failed. Test passed.
- Another nice thing about `doctest` is that you can write you test code separately into a txt file and perform the test with `python -m doctest -v txtfilefortest.txt`.
## `unittest` module
Python unit test module `unittest` is sometimes referred as PyUnit. There are a few concept closely related to the `unittest` module, including
- Test fixture, to perform preparation and cleanup actions
- Test Case, to test a functionality of the code
- Test suite, to combine multiple test case
- Test runner, to execute test
The work flow of performing a unit test with `unittest` is
- Build up class for test derived from `unittest` class.
- Write test function with name starts with `test_`.
### Build test cases
- Build up a class for test by defining a class and test functions
```python
class TestMethods(unittest.TestCase):
'''
class to define a set of unit test with Python unittest module
'''
def test_dist(self):
'''
test the function dist()
'''
self.assertEqual(dist(-7.83434151195,17.378188238,-17.5348765366,0.375603802,-0.0,0.0), 15.416540040627943)
def test_GPS2POS(self):
'''
test GPS2POS function
'''
self.assertEqual(GPS2POS((52.516288,13.377689)), (-6.48982764810209, 9.159322471000536))
pass
-
Basically, the test use a variety of
assertarguments shown in the following tableMethod 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)
Run test
-
Add
unittest.main()to your Python script. For example, my code looks like the following.if __name__ == '__main__': ''' __name__ to make sure the function be excuted when used as a script not as a loaded module ''' # doc test print "------------------- doctest -------------------" doctest.testmod() # unit test print "------------------- unittest -------------------" unittest.main() print "--------------- unittest fine tuned ------------" suite = unittest.TestSuite() suite.addTest(TestMethods('test_dist')) suite.addTest(TestMethods('test_GPS2POS')) unittest.TextTestRunner(verbosity=2).run(suite) # run actual code find_her() pass -
With command
python solution.py -vall test defined in the classTestMethodswill be executed and functions returns when all tests finished (without the rest of the function being executed). -
This can be avoided by defining a test suite with the following commands.
suite = unittest.TestSuite() suite.addTest(TestMethods('test_dist')) suite.addTest(TestMethods('test_GPS2POS')) unittest.TextTestRunner(verbosity=2).run(suite) -
The test suite will run all test defined within the suite and continue with the rest of the code when tests are done. The test will generate the following information printed on the screen.
test_dist (__main__.TestMethods) ... ok test_GPS2POS (__main__.TestMethods) ... ok -
As a alternative to
mainfunction and test suite, test functions defined in unit test class can be executed from command line directly.
guest37:tmpSolution su$ python -m unittest -v solution.TestMethods.test_dist test_dist (solution.TestMethods) … ok
Ran 1 test in 0.000s
OK guest37:tmpSolution su$ python -m unittest -v solution.TestMethods.test_GPS2POS test_GPS2POS (solution.TestMethods) … ok
Ran 1 test in 0.000s
OK guest37:tmpSolution su$ python -m unittest -v solution.TestMethods test_GPS2POS (solution.TestMethods) … ok test_dist (solution.TestMethods) … ok
Ran 2 tests in 0.000s
OK
### Other functionalities
- Run unit test with `unittest` for files with suffix `.py` in the directory with the following command.
```bash
$ python -m unittest discover -v -p *py
setupandteardownmethods.
nose module
nose is a third party module which does not come by default with Python. An installation is required. However, I don’t think I manage to install the package. But nose is at least working well for some basic test cases.
Write test case
The test case for nose is quite similar as unittest but is much simpler. I don’t need to define classes. Just go straight forwards to write a test function. The function that implemented the same test case as described above can be coded simply as the following.
def test_dist():
'''unit test with nose'''
assert dist(-7.83434151195,17.378188238,-17.5348765366,0.375603802,-0.0,0.0) == 15.416540040627943
pass
def test_GPS2POS():
'''unit test with nose'''
assert GPS2POS((52.516288,13.377689)) == (-6.48982764810209, 9.159322471000536)
pass
Run test
- With the following command, we can run a unit test built with
nose. The command can also run unit test coded withunittest.
nosetests -v solution.py
The command line argument -v is same as before which is to print detailed test information on screen. The result is shown as the follows.
test GPS2POS function ... ok
test the function dist() ... ok
unit test with nose ... ok
unit test with nose ... ok
----------------------------------------------------------------------
Ran 4 tests in 0.002s
OK
There are 4 tests in total in which the first two tests are built with unittest and the last two tests are built with nose.
- Use the following command to run all tests in a folder
nosetest -v *test.py.
setup and teardown functions in nose
-
With
nose, the setup and teardown functionalities can be implemented with simple Python function calls with prefixsetup_andteardown_. They are applied to test by calling a decorator@with_setup(setup_func,teardown_func). -
The following Python code implements a simple test with
setupandteardownfunctionalities to initialize and destroy a global variable for the unit test.
from nose.tools import * #--------------------- test with nose: decorator --------------------------- _globals = {‘tmpPoint’:None}
def setup_test_GPS2POS(): _globals[‘tmpPoint’] = (52.516288,13.377689) pass
def teardown_test_GPS2POS(): _globals[‘tmpPoint’] = None pass
@with_setup(setup_test_GPS2POS,teardown_test_GPS2POS) def test(): tmpPoint = _globals[‘tmpPoint’] assert GPS2POS(tmpPoint) == (-6.48982764810209, 9.159322471000536)
### Other functionalities with `nose`
- See [a complete collection of `nose` plugins](http://nose.readthedocs.org/en/latest/plugins/builtin.html). In particular, there are several interesting plugins
- Run doctest with `nose`, `nosetests --with-doctest -v solution_with_unittest`
- Profile code coverage with `nosetests --with-doctest --with-coverage -v solution_with_unittest`. The command will cover all Python modules imported after the code has been executed.
# External references
- [Beginning test-driven development in Python](http://code.tutsplus.com/tutorials/beginning-test-driven-development-in-python--net-30137)
- [Official Python document for `unittest`](https://docs.python.org/2/library/unittest.html)
- [Documentation for `nose` package](http://nose.readthedocs.org/en/latest/writing_tests.html)
- [Doctest introduction](http://pythontesting.net/framework/doctest/doctest-introduction/) is a tutorial about `pydoc` package to do unit test.
- [Pytest introduction](http://pythontesting.net/framework/pytest/pytest-introduction/#no_boilerplate) is a tutorial about `unittest` module to to unit test
- [Nose introduction](http://pythontesting.net/framework/nose/nose-introduction/#example) is a tutorial `nose` package for unit test.