Honestly? I’ve spent a ridiculous amount of time trying to figure out where to put my python unit test files. Sure, they worked fine located in the same directory as my source. However, the Felix Unger in me recoiled at the thought of my test files intermingling unsupervised with my main package source code. I’m pretty sure that’s how the Neanderthal went out.
After playing with __name__, relative imports, a half dozen folder configurations, IPython config tweaks, and in general just pulling my hair out at the roots as I chased the rabbit deeper into its hole, I decided to step back and revisit a tool I’d been meaning to check out for a long time: nose.
Sweet Judas Priest, the hours I could have saved! If you’re unfamiliar with nose, it extends the test loading and running features of unittest, making it easier to write, find and run tests. Its pure, buttery, test-running goodness.
What follows may be a bit basic for most experienced Pythonistas. For the rest of you Python noobs out there like me, here’s how I’m setting my packages and tests up now.
[text]
/MyPackage
/mypackage
__init__.py
spam.py
eggs.py
/tests
__init__.py
spam_tests.py
eggs_tests.py
[/text]
The source for my package goes under /mypackage. The __init__.py there contains the usual kind of stuff.
[python]
__all__ = [‘spam’, ‘eggs’]
[/python]
The unit tests for /mypackage go in /mypackage/tests. The __init__.py file in my /tests folder is empty. Since I’ve pretty much settled on nose, I’m not performing any additional test running tricks. Were I to add imports for my tests classes here similar to what I did in /mypackage/__init__.py, nose would run all my unit tests twice.
In my unit test files, I use relative imports to get at my source files. Relative paths only work within packages, so remember that the __init__.py files are required to make Python treat the directories as containing packages. For example, spam_tests.py would contain the following.
[python]
import unittest
from ..spam import Spam
class SpamTests(unittest.TestCase):
"""Unit tests for the Spam class."""
def testDefaultInitialization(self):
"""Verify the default values of a new Spam instance."""
s = Spam()
self.assertEqual(s.goteggs, False, ‘Spam already had eggs!’)
# etc…
[/python]
I don’t bother with adding if __name__ == '__main__' to the bottom of my test scripts in order to call unittest.main(). Setting the __name__ to '__main__' breaks relative imports.
Once I’ve got this basic layout configured, I’m ready to write my tests, implement the functionality in my classes, and verify everything works by running nosetests from my /tests directory.