Рекурсивный unittest обнаружить

У меня есть пакет с каталогом "тесты", в котором я храню свои модульные тесты. Мой пакет выглядит так:

.
├── LICENSE
├── models
│   └── __init__.py
├── README.md
├── requirements.txt
├── tc.py
├── tests
│   ├── db
│   │   └── test_employee.py
│   └── test_tc.py
└── todo.txt

В моем каталоге пакетов я хочу найти как tests/test_tc.py, так и tests/db/test_employee.py. Я бы предпочел не устанавливать стороннюю библиотеку (nose или т.д.), Или вам нужно вручную создать TestSuite для запуска этого.

Конечно, есть способ сказать unittest discover не прекращать смотреть, как только он найдет тест? python -m unittest discover -s tests найдет tests/test_tc.py и python -m unittest discover -s tests/db найдет tests/db/test_employee.py. Разве нет способа найти оба?

Ответ 1

При выполнении немного копания, кажется, что до тех пор, пока более глубокие модули остаются импортируемыми, они будут обнаружены через python -m unittest discover. Таким образом, решение было просто добавить файл __init__.py в каждый каталог, чтобы сделать их пакетами.

.
├── LICENSE
├── models
│   └── __init__.py
├── README.md
├── requirements.txt
├── tc.py
├── tests
│   ├── db
│   │   ├── __init__.py       # NEW
│   │   └── test_employee.py
│   ├── __init__.py           # NEW
│   └── test_tc.py
└── todo.txt

Пока каждый каталог имеет __init__.py, python -m unittest discover может импортировать соответствующий модуль test_*.

Ответ 2

Если вы можете добавить файл __init__.py внутри тестов, вы можете поместить там функцию load_tests, которая будет обрабатывать обнаружение для вас.

Если имя тестового пакета (каталог с __init__.py) соответствует         тогда пакет будет проверен на функцию load_tests. Если         это существует, тогда он будет вызываться с загрузчиком, тестами, шаблоном.

Если load_tests существует, то обнаружение не возвращается в пакет,         load_tests отвечает за загрузку всех тестов в пакете.

Я далек от уверенности в том, что это лучший способ, но один из способов написать эту функцию:

import os
import pkgutil
import inspect
import unittest

# Add *all* subdirectories to this module path
__path__ = [x[0] for x in os.walk(os.path.dirname(__file__))]

def load_tests(loader, suite, pattern):
    for imp, modname, _ in pkgutil.walk_packages(__path__):
        mod = imp.find_module(modname).load_module(modname)
        for memname, memobj in inspect.getmembers(mod):
            if inspect.isclass(memobj):
                if issubclass(memobj, unittest.TestCase):
                    print("Found TestCase: {}".format(memobj))
                    for test in loader.loadTestsFromTestCase(memobj):
                        print("  Found Test: {}".format(test))
                        suite.addTest(test)

    print("=" * 70)
    return suite

Довольно уродливый, я согласен.

Сначала вы добавляете все подкаталоги в путь тестовых пакетов (Docs).

Затем вы используете pkgutil, чтобы пройти путь, ища пакеты или модули.

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

Итак, теперь изнутри корня проекта вы можете ввести

python -m unittest discover -p tests

Использование переключателя шаблонов -p. Если все пойдет хорошо, вы увидите то, что я увидел, что-то вроде:

Found TestCase: <class 'test_tc.TestCase'>
  Found Test: testBar (test_tc.TestCase)
  Found Test: testFoo (test_tc.TestCase)
Found TestCase: <class 'test_employee.TestCase'>
  Found Test: testBar (test_employee.TestCase)
  Found Test: testFoo (test_employee.TestCase)
======================================================================
....
----------------------------------------------------------------------
Ran 4 tests in 0.001s

OK

Что и ожидалось, каждый из моих двух файлов-примеров содержал два теста: testFoo и testBar каждый.

Изменить: После некоторого дополнительного копания, похоже, вы могли бы указать эту функцию как:

def load_tests(loader, suite, pattern):
    for imp, modname, _ in pkgutil.walk_packages(__path__):
        mod = imp.find_module(modname).load_module(modname)
        for test in loader.loadTestsFromModule(mod):
            print("Found Tests: {}".format(test._tests))
            suite.addTests(test)

Здесь используется метод loader.loadTestsFromModule() вместо метода loader.loadTestsFromTestCase(), который я использовал выше. Он по-прежнему изменяет путь пакета tests и ищет его для модулей, который, я думаю, является ключевым здесь.

Теперь результат выглядит немного по-другому, так как мы добавляем найденный testuite за один раз к нашему главному тесту testuite suite:

python -m unittest discover -p tests
Found Tests: [<test_tc.TestCase testMethod=testBar>, <test_tc.TestCase testMethod=testFoo>]
Found Tests: [<test_employee.TestCase testMethod=testBar>, <test_employee.TestCase testMethod=testFoo>]
======================================================================
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK

Но мы по-прежнему получаем 4 теста, которые мы ожидали в обоих классах, в обоих подкаталогах.