Python: Как запустить unittest.main () для всех исходных файлов в подкаталоге? - PullRequest
57 голосов
/ 14 марта 2009

Я разрабатываю модуль 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.

Ответы [ 6 ]

59 голосов
/ 23 февраля 2012

Начиная с Python 2.7, обнаружение тестов автоматизировано в пакете unittest. Из документов :

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

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

cd project_directory
python -m unittest discover

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

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

Вместо вашего скрипта test.py.

29 голосов
/ 23 ноября 2012

Вот мой тестовый код обнаружения, который, кажется, делает эту работу. Я хотел убедиться, что смогу легко расширить тесты без необходимости перечислять их в любом из задействованных файлов, но также избегать записи всех тестов в одном файле Ü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.

26 голосов
/ 14 марта 2009

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

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

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

19 голосов
/ 14 марта 2009

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

% nosetests # finds all tests in all subdirectories
% nosetests tests/ # find all tests in the tests directory
2 голосов
/ 14 марта 2009

Я придумал фрагмент, который может делать то, что вы хотите. Он идет по пути, который вы предоставляете при поиске пакетов / модулей 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)
1 голос
/ 24 декабря 2014

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

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.)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...