Author: | Todd Greenwood |
Title: | Small Tutorial -- How to use DocTest, Unittest, and Python Packages |
Keywords: | |
Version: | 1.0 |
License: | GPL v. 2 |
Date: | Dec 01, 2005 |
I was struggling with creating a simple python test case that uses doctest, then unittest, then python eggs. Along the way I found out some things with respect to python packages. This is meant as a beginner example (and to help keep things fresh in my mind). I am writing this using Vim7 and Mikolaj Machowskihe's vst.vim plugin.
ContentsThe tutorial is composed of a very simple test class, small.py, that is modified in several small ways. Basically, there are two examples here:
Simple Case : No Nested Packages
Complex Case : Nested Packages
Here is what the src tree for the simple example looks like:
dir listing:
small/src/small/test/test.txt small/src/small/small2.py small/src/small/small.py small/src/small/small_unittest.pyc small/src/small/__init__.py small/src/small/small_unittest.py small/src/small_unittest.py small/setup_mult.py small/ez_setup.py small/setup.py
We have two test classes, small and small2.py. They look like this:
small.py:
#example method with doctest compatible docstring def removeme(): """ >>> import small as m >>> m.removeme() 'remove me' """ return 'remove me' def _test(): import doctest return doctest.testmod() if __name__ == "__main__": _test()
small2.py is almost identical, with the only difference in the method:
small2.py:
def saveme(): """ >>> import small2 as m >>> m.saveme() 'save me' """ return 'save me'
These two test modules show a simple use of the doctest module. These have to have some glue to the standard python unittests in order for the python egg test functionality to work. So, we'll add a python class to grok the modules and spit out unittest stuff.
small_unittest.py:
import small, small2 import unittest import doctest def getTestSuite(): suite = unittest.TestSuite() for mod in small,small2,: suite.addTest(doctest.DocTestSuite(mod)) return suite runner = unittest.TextTestRunner() runner.run(getTestSuite())
One of the things I thought would work would be to just import small, and the statement for mod in small,:
would find small.small and small.small2. Not the case. So I explicitly state the modules to test as for mod in small,small2,:
. Also note that we define a getTestSuite()
method. This is handy for setting up the python setuptools tests (shown below).
Run this test from small/src/small/
and you get:
small/src/small$ python small_unittest.py
:
.. ---------------------------------------------------------------------- Ran 2 tests in 0.004s OK
Now cd to small/src
and check out the unit test there:
small_unittest.py:
from small import small as s1 from small import small2 as s2 import unittest import doctest def getTestSuite(): suite = unittest.TestSuite() for mod in s1,s2,: suite.addTest(doctest.DocTestSuite(mod)) return suite runner = unittest.TextTestRunner() runner.run(getTestSuite())
The thing to note here is how small.small and small.small2 are referenced:
from small import small as s1 from small import small2 as s2
For some reason, this gave me plenty of problems. Hence this tutorial.
Now cd to small
and check out the python egg stuff:
setup.py:
from ez_setup import use_setuptools use_setuptools() from setuptools import setup, find_packages setup(name = "small", version = "0.1", description = "test", author = "Todd Greenwood", author_email = "t.greenwoodgeer@gmail.com", url = "http://www.angelfire.com/planet/tango", packages = find_packages('src'), package_dir = {'':'src'}, package_data = {'small':['test/*.txt']}, test_suite = 'small.small_unittest.getTestSuite', license = "GNU Lesser General Public License", classifiers = [ "Development Status :: 1 - Alpha", "Intended Audience :: Developers", "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", "Programming Language :: Python", "Topic :: Database :: Front-Ends", ] )
The thing to notice here is this line:
test_suite = 'small.small_unittest.getTestSuite',
This shows how to reference the modules, and how to get the test suite from the unittest. Also note the package_data line...that's how you include other files in the package. See peak for more.
Check out the peak website for more info on setuptools and the egg utilities. For now, we'll do two things, run the tests, and then create an egg distribution.
Run the tests:
small$ python setup.py test
:
running test running egg_info writing src/small.egg-info/PKG-INFO writing top-level names to src/small.egg-info/top_level.txt running build_ext .. ---------------------------------------------------------------------- Ran 2 tests in 0.005s OK Doctest: small.small.removeme ... ok Doctest: small.small2.saveme ... ok ---------------------------------------------------------------------- Ran 2 tests in 0.005s OK
Create a distribution with python setup.py bdist_egg
:
small$ python setup.py bdist_egg
:
running bdist_egg running egg_info writing src/small.egg-info/PKG-INFO writing top-level names to src/small.egg-info/top_level.txt installing library code to build/bdist.linux-i686/egg running install_lib running build_py copying src/small/small2.py -> build/lib/small copying src/small/small.py -> build/lib/small copying src/small/__init__.py -> build/lib/small copying src/small/small_unittest.py -> build/lib/small copying src/small/test/test.txt -> build/lib/small/test creating build/bdist.linux-i686/egg creating build/bdist.linux-i686/egg/.svn creating build/bdist.linux-i686/egg/.svn/text-base creating build/bdist.linux-i686/egg/.svn/prop-base creating build/bdist.linux-i686/egg/.svn/props creating build/bdist.linux-i686/egg/.svn/wcprops creating build/bdist.linux-i686/egg/.svn/tmp creating build/bdist.linux-i686/egg/.svn/tmp/text-base creating build/bdist.linux-i686/egg/.svn/tmp/prop-base creating build/bdist.linux-i686/egg/.svn/tmp/props creating build/bdist.linux-i686/egg/.svn/tmp/wcprops copying build/lib/.svn/entries -> build/bdist.linux-i686/egg/.svn copying build/lib/.svn/empty-file -> build/bdist.linux-i686/egg/.svn copying build/lib/.svn/README.txt -> build/bdist.linux-i686/egg/.svn copying build/lib/.svn/format -> build/bdist.linux-i686/egg/.svn creating build/bdist.linux-i686/egg/small creating build/bdist.linux-i686/egg/small/.svn creating build/bdist.linux-i686/egg/small/.svn/text-base copying build/lib/small/.svn/text-base/small.py.svn-base -> build/bdist.linux-i686/egg/small/.svn/text-base copying build/lib/small/.svn/text-base/__init__.py.svn-base -> build/bdist.linux-i686/egg/small/.svn/text-base copying build/lib/small/.svn/text-base/small_unittest.py.svn-base -> build/bdist.linux-i686/egg/small/.svn/text-base creating build/bdist.linux-i686/egg/small/.svn/prop-base copying build/lib/small/.svn/prop-base/small.py.svn-base -> build/bdist.linux-i686/egg/small/.svn/prop-base copying build/lib/small/.svn/prop-base/__init__.py.svn-base -> build/bdist.linux-i686/egg/small/.svn/prop-base copying build/lib/small/.svn/prop-base/small_unittest.py.svn-base -> build/bdist.linux-i686/egg/small/.svn/prop-base creating build/bdist.linux-i686/egg/small/.svn/props copying build/lib/small/.svn/props/small.py.svn-work -> build/bdist.linux-i686/egg/small/.svn/props copying build/lib/small/.svn/props/__init__.py.svn-work -> build/bdist.linux-i686/egg/small/.svn/props copying build/lib/small/.svn/props/small_unittest.py.svn-work -> build/bdist.linux-i686/egg/small/.svn/props creating build/bdist.linux-i686/egg/small/.svn/wcprops creating build/bdist.linux-i686/egg/small/.svn/tmp creating build/bdist.linux-i686/egg/small/.svn/tmp/text-base creating build/bdist.linux-i686/egg/small/.svn/tmp/prop-base creating build/bdist.linux-i686/egg/small/.svn/tmp/props creating build/bdist.linux-i686/egg/small/.svn/tmp/wcprops copying build/lib/small/.svn/entries -> build/bdist.linux-i686/egg/small/.svn copying build/lib/small/.svn/empty-file -> build/bdist.linux-i686/egg/small/.svn copying build/lib/small/.svn/README.txt -> build/bdist.linux-i686/egg/small/.svn copying build/lib/small/.svn/format -> build/bdist.linux-i686/egg/small/.svn creating build/bdist.linux-i686/egg/small/test creating build/bdist.linux-i686/egg/small/test/.svn creating build/bdist.linux-i686/egg/small/test/.svn/text-base copying build/lib/small/test/.svn/text-base/test.txt.svn-base -> build/bdist.linux-i686/egg/small/test/.svn/text-base creating build/bdist.linux-i686/egg/small/test/.svn/prop-base copying build/lib/small/test/.svn/prop-base/test.txt.svn-base -> build/bdist.linux-i686/egg/small/test/.svn/prop-base creating build/bdist.linux-i686/egg/small/test/.svn/props copying build/lib/small/test/.svn/props/test.txt.svn-work -> build/bdist.linux-i686/egg/small/test/.svn/props creating build/bdist.linux-i686/egg/small/test/.svn/wcprops creating build/bdist.linux-i686/egg/small/test/.svn/tmp creating build/bdist.linux-i686/egg/small/test/.svn/tmp/text-base creating build/bdist.linux-i686/egg/small/test/.svn/tmp/prop-base creating build/bdist.linux-i686/egg/small/test/.svn/tmp/props creating build/bdist.linux-i686/egg/small/test/.svn/tmp/wcprops copying build/lib/small/test/.svn/entries -> build/bdist.linux-i686/egg/small/test/.svn copying build/lib/small/test/.svn/empty-file -> build/bdist.linux-i686/egg/small/test/.svn copying build/lib/small/test/.svn/README.txt -> build/bdist.linux-i686/egg/small/test/.svn copying build/lib/small/test/.svn/format -> build/bdist.linux-i686/egg/small/test/.svn copying build/lib/small/test/test.txt -> build/bdist.linux-i686/egg/small/test copying build/lib/small/__init__.py -> build/bdist.linux-i686/egg/small copying build/lib/small/small.py -> build/bdist.linux-i686/egg/small copying build/lib/small/small2.py -> build/bdist.linux-i686/egg/small copying build/lib/small/small_unittest.py -> build/bdist.linux-i686/egg/small byte-compiling build/bdist.linux-i686/egg/small/__init__.py to __init__.pyc byte-compiling build/bdist.linux-i686/egg/small/small.py to small.pyc byte-compiling build/bdist.linux-i686/egg/small/small2.py to small2.pyc byte-compiling build/bdist.linux-i686/egg/small/small_unittest.py to small_unittest.pyc creating build/bdist.linux-i686/egg/EGG-INFO copying src/small.egg-info/top_level.txt -> build/bdist.linux-i686/egg/EGG-INFO copying src/small.egg-info/PKG-INFO -> build/bdist.linux-i686/egg/EGG-INFO zip_safe flag not set; analyzing archive contents... creating 'dist/small-0.1-py2.4.egg' and adding 'build/bdist.linux-i686/egg' to it removing 'build/bdist.linux-i686/egg' (and everything under it)
The complex case splits out the test modules into a nested package hierarchy like this:
package hierarchy:
small/src/ small/src/size/ small/src/size/small small/src/size/large
The dir listing below should make this clear:
dir listing:
small/mult_tree_src/size/small/test/test.txt small/mult_tree_src/size/small/small.pyc small/mult_tree_src/size/small/small.py small/mult_tree_src/size/small/small_unittest.pyc small/mult_tree_src/size/small/__init__.py small/mult_tree_src/size/small/__init__.pyc small/mult_tree_src/size/small/small_unittest.py small/mult_tree_src/size/large small/mult_tree_src/size/large/large_unittest.py small/mult_tree_src/size/large/large.py small/mult_tree_src/size/large/large.pyc small/mult_tree_src/size/large/__init__.py small/mult_tree_src/size/large/__init__.pyc small/mult_tree_src/size/__init__.py small/mult_tree_src/size/__init__.pyc small/mult_tree_src/small-mult-test.egg-info small/mult_tree_src/small-mult-test.egg-info/top_level.txt small/mult_tree_src/small-mult-test.egg-info/PKG-INFO small/mult_tree_src/size_unittest.pyc small/mult_tree_src/size_unittest.py
small/mult_tree_src/size/small/small.py:
#example method with doctest compatible docstring def smallme(): """ >>> import small as m >>> m.smallme() 'small me' """ return 'small me' def _test(): import doctest return doctest.testmod() if __name__ == "__main__": _test()
The only difference between small.py and large.py is the name of the method.
small/mult_tree_src/size/large/large.py:
def largeme(): """ >>> import large as l >>> l.largeme() 'large me' """ return 'large me'
Again, the only difference between the unit tests is the import and the name of the module adedd to the test suite.
small/mult_tree_src/size/small/small_unittest.py:
import small import unittest import doctest def getTestSuite(): suite = unittest.TestSuite() for mod in small,: suite.addTest(doctest.DocTestSuite(mod)) return suite runner = unittest.TextTestRunner() runner.run(getTestSuite())
small/mult_tree_src/size/large/large_unittest.py:
import large import unittest import doctest def getTestSuite(): suite = unittest.TestSuite() for mod in large,: suite.addTest(doctest.DocTestSuite(mod)) return suite runner = unittest.TextTestRunner() runner.run(getTestSuite())
The unittests for size are:
small/mult_tree_src/size_unittest.py:
from size.small import small from size.large import large import unittest import doctest def getTestSuite(): suite = unittest.TestSuite() for mod in small,large,: suite.addTest(doctest.DocTestSuite(mod)) return suite runner = unittest.TextTestRunner() runner.run(getTestSuite())
So, again, the thing to look at is how the import works (and again, the thing that threw me for awhile):
note:
from size.small import small from size.large import large
small/setup_mult.py:
from ez_setup import use_setuptools use_setuptools() from setuptools import setup, find_packages setup(name = "small_mult_test", version = "0.1", description = "test", author = "Todd Greenwood", author_email = "t.greenwoodgeer@gmail.com", url = "http://www.angelfire.com/planet/tango", packages = find_packages('mult_tree_src'), package_dir = {'':'mult_tree_src'}, package_data = {'':['test/*.txt']}, test_suite = 'size_unittest.getTestSuite', license = "GNU Lesser General Public License", classifiers = [ "Development Status :: 1 - Alpha", "Intended Audience :: Developers", "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", "Programming Language :: Python", "Topic :: Database :: Front-Ends", ] )
Things to note:
test_suite : again, we reference the fx that gives us the test suite to run, in this case, it is:
def getTestSuite(): suite = unittest.TestSuite() for mod in small,large,: suite.addTest(doctest.DocTestSuite(mod)) return suite
package data : i'm not doing much with that now, but some tests in other packages that I'm writing place test data there.
url : need to see if that works for downloading dependencies (next tutorial, not this one)
Run the tests for size :
small$ python setup_mult.py test
:
running test running egg_info writing mult_tree_src/small-mult-test.egg-info/PKG-INFO writing top-level names to mult_tree_src/small-mult-test.egg-info/top_level.txt running build_ext .. ---------------------------------------------------------------------- Ran 2 tests in 0.009s OK Doctest: size.small.small.smallme ... ok Doctest: size.large.large.largeme ... ok ---------------------------------------------------------------------- Ran 2 tests in 0.002s OK
Build the egg dist for size:
small$ python setup_mult.py bdist_egg
:
running bdist_egg running egg_info writing mult_tree_src/small-mult-test.egg-info/PKG-INFO writing top-level names to mult_tree_src/small-mult-test.egg-info/top_level.txt installing library code to build/bdist.linux-i686/egg running install_lib running build_py creating build/lib/size copying mult_tree_src/size/__init__.py -> build/lib/size creating build/lib/size/small copying mult_tree_src/size/small/small.py -> build/lib/size/small copying mult_tree_src/size/small/__init__.py -> build/lib/size/small copying mult_tree_src/size/small/small_unittest.py -> build/lib/size/small creating build/lib/size/large copying mult_tree_src/size/large/large_unittest.py -> build/lib/size/large copying mult_tree_src/size/large/large.py -> build/lib/size/large copying mult_tree_src/size/large/__init__.py -> build/lib/size/large creating build/lib/size/small/test copying mult_tree_src/size/small/test/test.txt -> build/lib/size/small/test creating build/bdist.linux-i686/egg creating build/bdist.linux-i686/egg/.svn creating build/bdist.linux-i686/egg/.svn/text-base creating build/bdist.linux-i686/egg/.svn/prop-base creating build/bdist.linux-i686/egg/.svn/props creating build/bdist.linux-i686/egg/.svn/wcprops creating build/bdist.linux-i686/egg/.svn/tmp creating build/bdist.linux-i686/egg/.svn/tmp/text-base creating build/bdist.linux-i686/egg/.svn/tmp/prop-base creating build/bdist.linux-i686/egg/.svn/tmp/props creating build/bdist.linux-i686/egg/.svn/tmp/wcprops copying build/lib/.svn/entries -> build/bdist.linux-i686/egg/.svn copying build/lib/.svn/empty-file -> build/bdist.linux-i686/egg/.svn copying build/lib/.svn/README.txt -> build/bdist.linux-i686/egg/.svn copying build/lib/.svn/format -> build/bdist.linux-i686/egg/.svn creating build/bdist.linux-i686/egg/small creating build/bdist.linux-i686/egg/small/.svn creating build/bdist.linux-i686/egg/small/.svn/text-base copying build/lib/small/.svn/text-base/small.py.svn-base -> build/bdist.linux-i686/egg/small/.svn/text-base copying build/lib/small/.svn/text-base/__init__.py.svn-base -> build/bdist.linux-i686/egg/small/.svn/text-base copying build/lib/small/.svn/text-base/small_unittest.py.svn-base -> build/bdist.linux-i686/egg/small/.svn/text-base creating build/bdist.linux-i686/egg/small/.svn/prop-base copying build/lib/small/.svn/prop-base/small.py.svn-base -> build/bdist.linux-i686/egg/small/.svn/prop-base copying build/lib/small/.svn/prop-base/__init__.py.svn-base -> build/bdist.linux-i686/egg/small/.svn/prop-base copying build/lib/small/.svn/prop-base/small_unittest.py.svn-base -> build/bdist.linux-i686/egg/small/.svn/prop-base creating build/bdist.linux-i686/egg/small/.svn/props copying build/lib/small/.svn/props/small.py.svn-work -> build/bdist.linux-i686/egg/small/.svn/props copying build/lib/small/.svn/props/__init__.py.svn-work -> build/bdist.linux-i686/egg/small/.svn/props copying build/lib/small/.svn/props/small_unittest.py.svn-work -> build/bdist.linux-i686/egg/small/.svn/props creating build/bdist.linux-i686/egg/small/.svn/wcprops creating build/bdist.linux-i686/egg/small/.svn/tmp creating build/bdist.linux-i686/egg/small/.svn/tmp/text-base creating build/bdist.linux-i686/egg/small/.svn/tmp/prop-base creating build/bdist.linux-i686/egg/small/.svn/tmp/props creating build/bdist.linux-i686/egg/small/.svn/tmp/wcprops copying build/lib/small/.svn/entries -> build/bdist.linux-i686/egg/small/.svn copying build/lib/small/.svn/empty-file -> build/bdist.linux-i686/egg/small/.svn copying build/lib/small/.svn/README.txt -> build/bdist.linux-i686/egg/small/.svn copying build/lib/small/.svn/format -> build/bdist.linux-i686/egg/small/.svn creating build/bdist.linux-i686/egg/small/test creating build/bdist.linux-i686/egg/small/test/.svn creating build/bdist.linux-i686/egg/small/test/.svn/text-base copying build/lib/small/test/.svn/text-base/test.txt.svn-base -> build/bdist.linux-i686/egg/small/test/.svn/text-base creating build/bdist.linux-i686/egg/small/test/.svn/prop-base copying build/lib/small/test/.svn/prop-base/test.txt.svn-base -> build/bdist.linux-i686/egg/small/test/.svn/prop-base creating build/bdist.linux-i686/egg/small/test/.svn/props copying build/lib/small/test/.svn/props/test.txt.svn-work -> build/bdist.linux-i686/egg/small/test/.svn/props creating build/bdist.linux-i686/egg/small/test/.svn/wcprops creating build/bdist.linux-i686/egg/small/test/.svn/tmp creating build/bdist.linux-i686/egg/small/test/.svn/tmp/text-base creating build/bdist.linux-i686/egg/small/test/.svn/tmp/prop-base creating build/bdist.linux-i686/egg/small/test/.svn/tmp/props creating build/bdist.linux-i686/egg/small/test/.svn/tmp/wcprops copying build/lib/small/test/.svn/entries -> build/bdist.linux-i686/egg/small/test/.svn copying build/lib/small/test/.svn/empty-file -> build/bdist.linux-i686/egg/small/test/.svn copying build/lib/small/test/.svn/README.txt -> build/bdist.linux-i686/egg/small/test/.svn copying build/lib/small/test/.svn/format -> build/bdist.linux-i686/egg/small/test/.svn copying build/lib/small/test/test.txt -> build/bdist.linux-i686/egg/small/test copying build/lib/small/__init__.py -> build/bdist.linux-i686/egg/small copying build/lib/small/small.py -> build/bdist.linux-i686/egg/small copying build/lib/small/small2.py -> build/bdist.linux-i686/egg/small copying build/lib/small/small_unittest.py -> build/bdist.linux-i686/egg/small creating build/bdist.linux-i686/egg/size copying build/lib/size/__init__.py -> build/bdist.linux-i686/egg/size creating build/bdist.linux-i686/egg/size/small copying build/lib/size/small/small.py -> build/bdist.linux-i686/egg/size/small copying build/lib/size/small/__init__.py -> build/bdist.linux-i686/egg/size/small copying build/lib/size/small/small_unittest.py -> build/bdist.linux-i686/egg/size/small creating build/bdist.linux-i686/egg/size/small/test copying build/lib/size/small/test/test.txt -> build/bdist.linux-i686/egg/size/small/test creating build/bdist.linux-i686/egg/size/large copying build/lib/size/large/large_unittest.py -> build/bdist.linux-i686/egg/size/large copying build/lib/size/large/large.py -> build/bdist.linux-i686/egg/size/large copying build/lib/size/large/__init__.py -> build/bdist.linux-i686/egg/size/large byte-compiling build/bdist.linux-i686/egg/small/__init__.py to __init__.pyc byte-compiling build/bdist.linux-i686/egg/small/small.py to small.pyc byte-compiling build/bdist.linux-i686/egg/small/small2.py to small2.pyc byte-compiling build/bdist.linux-i686/egg/small/small_unittest.py to small_unittest.pyc byte-compiling build/bdist.linux-i686/egg/size/__init__.py to __init__.pyc byte-compiling build/bdist.linux-i686/egg/size/small/small.py to small.pyc byte-compiling build/bdist.linux-i686/egg/size/small/__init__.py to __init__.pyc byte-compiling build/bdist.linux-i686/egg/size/small/small_unittest.py to small_unittest.pyc byte-compiling build/bdist.linux-i686/egg/size/large/large_unittest.py to large_unittest.pyc byte-compiling build/bdist.linux-i686/egg/size/large/large.py to large.pyc byte-compiling build/bdist.linux-i686/egg/size/large/__init__.py to __init__.pyc creating build/bdist.linux-i686/egg/EGG-INFO copying mult_tree_src/small-mult-test.egg-info/top_level.txt -> build/bdist.linux-i686/egg/EGG-INFO copying mult_tree_src/small-mult-test.egg-info/PKG-INFO -> build/bdist.linux-i686/egg/EGG-INFO zip_safe flag not set; analyzing archive contents... creating 'dist/small_mult_test-0.1-py2.4.egg' and adding 'build/bdist.linux-i686/egg' to it removing 'build/bdist.linux-i686/egg' (and everything under it)
Ok, that's it for now.