views:

348

answers:

3

Here's what I want to do: I want to build a test suite that's organized into packages like tests.ui, tests.text, tests.fileio, etc. In each __init__.py in these packages, I want to make a test suite consisting of all the tests in all the modules in that package. Of course, getting all the tests can be done with unittest.TestLoader, but it seems that I have to add each module individually. So supposing that test.ui has editor_window_test.py and preview_window_test.py, I want the __init__.py to import these two files and get a list of the two module objects. The idea is that I want to automate making the test suites so that I can't forget to include something in the test suite.

What's the best way to do this? It seems like it would be an easy thing to do, but I'm not finding anything.

I'm using Python 2.5 btw.

+1  A: 

You can use os.listdir to find all files in the test.* directory and then filter out .py files:

# Place this code to your __init__.py in test.* directory
import os
modules = []
for name in os.listdir(os.path.dirname(os.path.abspath(__file__))):
    m, ext = os.path.splitext()
    if ext == '.py':
        modules.append(__import__(m))
__all__ = modules

The magic variable __file__ contains filepath of the current module. Try

print __file__

to check.

bialix
(Its a bit unpythonic to auto-import submodules like the OP wants. Putting `import a, b, c` in __init__.py is just as easy). Anyways, missing steps:`modules.append(__import__(name))`, then `__all__ = modules`.
Richard Levasseur
you're right, fixed.
bialix
It appears that this also imports by filepath which only works because of a bug in pre-2.6 python.
Jason Baker
it's not bug, it's relative import.
bialix
+1  A: 

Solution to exactly this problem from our django project:

"""Test loader for all module tests
"""
import unittest
import re, os, imp, sys

def find_modules(package):
    files = [re.sub('\.py$', '', f) for f in os.listdir(os.path.dirname(package.__file__))
             if f.endswith(".py")]
    return [imp.load_module(file, *imp.find_module(file, package.__path__)) for file in files]

def suite(package=None):
    """Assemble test suite for Django default test loader"""
    if not package: package = myapp.tests # Default argument required for Django test runner
    return unittest.TestSuite([unittest.TestLoader().loadTestsFromModule(m)
                               for m in find_modules(package)])

if __name__ == '__main__':
    unittest.TextTestRunner().run(suite(myapp.tests))

EDIT: The benefit compared to bialix's solution is that you can place this loader anytwhere in the project tree, there's no need to modify init.py in every test directory.

Alex Lebedev
+1  A: 

Good answers here, but the best thing to do would be to use a 3rd party test discovery and runner like:

  • Nose (my favourite)
  • Trial (pretty nice, especially when testing async stuff)
  • py.test (less good, in my opinion)

They are all compatible with plain unittest.TestCase and you won't have to modify your tests in any way, neither would you have to use the advanced features in any of them. Just use as a suite discovery.

Is there a specific reason you want to reinvent the nasty stuff in these libs?

Ali A
No, not really. I didn't know about them. :-)
Jason Baker
Ah, cool, well use one. I can strongly recommend Nose.
Ali A