Подтвердить что ты не робот

Python: как запустить unittest.main() для всех исходных файлов в подкаталоге?

Я разрабатываю модуль Python с несколькими исходными файлами, каждый из которых имеет свой собственный тестовый класс, полученный из unittest прямо в источнике. Рассмотрим структуру каталогов:

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

Чтобы проверить Foo.py или Bar.py, я бы добавил это в конце исходных файлов Foo.py и Bar.py:

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

И запустите Python в любом из источников, т.е.

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

OK

В идеале, я бы "test.py" автоматически выполнил поиск dirBar для любых производных классов unittest и сделал один вызов "unittest.main()". Какой лучший способ сделать это на практике?

Я попытался использовать Python для вызова execfile для каждого *.py файла в dirBar, который запускается один раз для первого .py файла и выйдет из вызывающего test.py, а затем мне придется дублировать мой код, добавив unittest.main() в каждый исходный файл, что нарушает принципы DRY.

4b9b3361

Ответ 1

Я знал, что есть очевидное решение:

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

Содержимое dirFoo/test.py

from dirBar import *
import unittest

if __name__ == "__main__":

    unittest.main()

Запустите тесты:

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

OK

Извините за глупый вопрос.

Ответ 2

По состоянию на Python 2.7 обнаружение тестов автоматизировано в пакете unittest. Из docs:

Unittest поддерживает простое обнаружение тестов. Чтобы быть совместимым с обнаружением теста все тестовые файлы должны быть модулями или пакетами импортируется из каталога верхнего уровня проекта (это означает что их имена должны быть действительными идентификаторами).

Открытие теста выполняется в TestLoader.discover(), но может также использовать из командной строки. Основное использование командной строки:

cd project_directory
python -m unittest discover

По умолчанию он ищет пакеты с именем test*.py, но это можно изменить, чтобы вы могли использовать что-то вроде

python -m unittest discover --pattern=*.py

Вместо вашего test.py script.

Ответ 3

Вот мой тестовый код обнаружения, который, похоже, выполняет эту работу. Я хотел удостовериться, что могу легко продлить тесты без необходимости перечислять их в любом из задействованных файлов, но также не записывать все тесты в одном файле Übertest.

Итак, структура

myTests.py
testDir\
    __init__.py
    testA.py
    testB.py

myTest.py выглядит так:

import unittest

if __name__ == '__main__':
    testsuite = unittest.TestLoader().discover('.')
    unittest.TextTestRunner(verbosity=1).run(testsuite)

Я считаю, что это самое простое решение для написания нескольких тестовых примеров в одном каталоге. Для решения требуется Python 2.7 или Python 3.

Ответ 4

Вам следует попробовать nose. Это библиотека, которая помогает создавать тесты и интегрируется с unittest или doctest. Все, что вам нужно сделать, это запустить nosetests, и он найдет для вас все ваши unittests.

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

Ответ 5

Я придумал фрагмент, который может делать то, что вы хотите. Он проходит путь, который вы предоставляете для поиска пакетов/модулей Python, и накапливает набор тестовых наборов из этих модулей, которые затем выполняются сразу.

Самое приятное в том, что он будет работать на всех пакетах, вложенных в указанный вами каталог, и вам не придется вручную изменять импорт при добавлении новых компонентов.

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)

Ответ 6

В случае, если это помогает кому-либо, вот подход, к которому я пришел, для решения этой проблемы. У меня был вариант использования, где у меня есть следующая структура каталогов:

mypackage/
    tests/
        test_category_1/
            tests_1a.py
            tests_1b.py
            ...
        test_category_2/
            tests_2a.py
            tests_2b.py
            ...
        ...

и я хочу, чтобы все следующие действия работали очевидным образом и чтобы были предоставлены те же аргументы командной строки, которые приняты unittest:

python -m mypackage.tests
python -m mypackage.tests.test_category_1
python -m mypackage.tests.test_category_1.tests_1a

Решением было настроить mypackage/tests/__init__.py следующим образом:

import unittest

def prepare_load_tests_function (the__path__):
    test_suite = unittest.TestLoader().discover(the__path__[0])
    def load_tests (_a, _b, _c):
        return test_suite
    return load_tests

и настроить mypackage/tests/__main__.py следующим образом:

import unittest
from . import prepare_load_tests_function, __path__

load_tests = prepare_load_tests_function(__path__)
unittest.main()

и скопировать и вставить пустой __init__.py и следующий __main__.py в каждый mypackage/tests/test_category_n/:

import unittest
from .. import prepare_load_tests_function
from . import __path__

load_tests = prepare_load_tests_function(__path__)
unittest.main()

а также добавить стандартный if __name__ == '__main__': unittest.main() в каждый файл фактических тестов.

(работает для меня на Python 3.3 в Windows, ymmv.)