views:

1142

answers:

3

I am developing a Python module with several source files, each with its own test class derived from unittest right in the source. Consider the directory structure:

dirFoo\
    test.py
    dirBar\
        __init__.py
        Foo.py
        Bar.py

To test either Foo.py or Bar.py, I would add this at the end of the Foo.py and Bar.py source files:

if __name__ == "__main__":
    unittest.main()

And run Python on either source, i.e.

$ python Foo.py
...........
----------------------------------------------------------------------
Ran 11 tests in 2.314s

OK

Ideally, I would have "test.py" automagically search dirBar for any unittest derived classes and make one call to "unittest.main()". What's the best way to do this in practice?

I tried using Python to call execfile for every *.py file in dirBar, which runs once for the first .py file found & exits the calling test.py, plus then I have to duplicate my code by adding unittest.main() in every source file--which violates DRY principles.

+6  A: 

I knew there was an obvious solution:

dirFoo\
    test.py
    dirBar\
        Foo.py
        Bar.py

Contents of dirFoo/test.py

from dirBar import *
import unittest

if __name__ == "__main__":

    unittest.main()

Run the tests:

$ python test.py
...........
----------------------------------------------------------------------
Ran 11 tests in 2.305s

OK

Sorry for the silly question.

Pete
Either you've missed 'dirBar/__init__.py` or `from dirBar import *` won't work. btw, use lowercase for package/module names.
J.F. Sebastian
Also, the problem with this is that you assume the test cases of Foo.py and Bar.py are exposed in the dirBar __init__.py module. If they're not, your test.py won't test anything.
cdleary
+5  A: 

You should try nose. It's a library to help create tests and it integrates with unittest or doctest. All you need to do is run nosetests and it'll find all your unittests for you.

% nosetests # finds all tests in all subdirectories
% nosetests tests/ # find all tests in the tests directory
Cristian
A: 

I came up with a snippet that may do what you want. It walks a path that you provide looking for Python packages/modules and accumulates a set of test suites from those modules, which it then executes all at once.

The nice thing about this is that it will work on all packages nested under the directory you specify, and you won't have to manually change the imports as you add new components.

import logging
import os
import unittest

MODULE_EXTENSIONS = set('.py .pyc .pyo'.split())

def unit_test_extractor(tup, path, filenames):
    """Pull ``unittest.TestSuite``s from modules in path
    if the path represents a valid Python package. Accumulate
    results in `tup[1]`.
    """
    package_path, suites = tup
    logging.debug('Path: %s', path)
    logging.debug('Filenames: %s', filenames)
    relpath = os.path.relpath(path, package_path)
    relpath_pieces = relpath.split(os.sep)

    if relpath_pieces[0] == '.': # Base directory.
        relpath_pieces.pop(0) # Otherwise, screws up module name.
    elif not any(os.path.exists(os.path.join(path, '__init__' + ext))
            for ext in MODULE_EXTENSIONS):
        return # Not a package directory and not the base directory, reject.

    logging.info('Base: %s', '.'.join(relpath_pieces))
    for filename in filenames:
        base, ext = os.path.splitext(filename)
        if ext not in MODULE_EXTENSIONS: # Not a Python module.
            continue
        logging.info('Module: %s', base)
        module_name = '.'.join(relpath_pieces + [base])
        logging.info('Importing from %s', module_name)
        module = __import__(module_name)
        module_suites = unittest.defaultTestLoader.loadTestsFromModule(module)
        logging.info('Got suites: %s', module_suites)
        suites += module_suites

def get_test_suites(path):
    """:return: Iterable of suites for the packages/modules
    present under :param:`path`.
    """
    logging.info('Base path: %s', package_path)
    suites = []
    os.path.walk(package_path, unit_test_extractor, (package_path, suites))
    logging.info('Got suites: %s', suites)
    return suites

if __name__ == '__main__':
    logging.basicConfig(level=logging.WARN)
    package_path = os.path.dirname(os.path.abspath(__file__))
    suites = get_test_suites(package_path)
    for suite in suites:
        unittest.TextTestRunner(verbosity=2).run(suite)
cdleary