Как запустить все модульные тесты Python в каталоге? - PullRequest
244 голосов
/ 14 ноября 2009

У меня есть каталог, содержащий мои модульные тесты Python. Каждый модуль модульного тестирования имеет форму test _ *. Py . Я пытаюсь создать файл с именем all_test.py , который, как вы уже догадались, запустит все файлы в вышеупомянутой тестовой форме и вернет результат. Я пробовал два метода до сих пор; оба потерпели неудачу. Я покажу два метода, и я надеюсь, что кто-то там знает, как на самом деле сделать это правильно.

Для моей первой отважной попытки я подумал: «Если я просто импортирую все свои модули тестирования в файл, а затем назову этот unittest.main() doodad, он будет работать, верно?" Ну, оказывается, я был не прав.

import glob
import unittest

testSuite = unittest.TestSuite()
test_file_strings = glob.glob('test_*.py')
module_strings = [str[0:len(str)-3] for str in test_file_strings]

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

Это не сработало, результат, который я получил:

$ python all_test.py 

----------------------------------------------------------------------
Ran 0 tests in 0.000s

OK

Для моей второй попытки, хотя, хорошо, может быть, я попытаюсь сделать все это тестирование более "ручным" способом. Поэтому я попытался сделать это ниже:

import glob
import unittest

testSuite = unittest.TestSuite()
test_file_strings = glob.glob('test_*.py')
module_strings = [str[0:len(str)-3] for str in test_file_strings]
[__import__(str) for str in module_strings]
suites = [unittest.TestLoader().loadTestsFromName(str) for str in module_strings]
[testSuite.addTest(suite) for suite in suites]
print testSuite 

result = unittest.TestResult()
testSuite.run(result)
print result

#Ok, at this point I have a result
#How do I display it as the normal unit test command line output?
if __name__ == "__main__":
    unittest.main()

Это тоже не сработало, но кажется, что так близко!

$ python all_test.py 
<unittest.TestSuite tests=[<unittest.TestSuite tests=[<unittest.TestSuite tests=[<test_main.TestMain testMethod=test_respondes_to_get>]>]>]>
<unittest.TestResult run=1 errors=0 failures=0>

----------------------------------------------------------------------
Ran 0 tests in 0.000s

OK

Кажется, у меня есть какой-то набор, и я могу выполнить результат. Я немного обеспокоен тем фактом, что он говорит, что у меня только run=1, кажется, что это должно быть run=2, но это прогресс Но как мне передать и отобразить результат на главном? Или как мне заставить его работать, чтобы я мог просто запустить этот файл, и при этом запустить все модульные тесты в этом каталоге?

Ответы [ 14 ]

386 голосов
/ 26 марта 2013

С Python 2.7 и выше вам не нужно писать новый код или использовать сторонние инструменты для этого; Выполнение рекурсивного теста через командную строку является встроенным.

python -m unittest discover <test_directory>
# or
python -m unittest discover -s <directory> -p '*_test.py'

Вы можете прочитать больше в Python 2.7 или python 3.x документация unittest.

98 голосов
/ 14 ноября 2009

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

Обновлен:

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

testmodules = [
    'cogapp.test_makefiles',
    'cogapp.test_whiteutils',
    'cogapp.test_cogapp',
    ]

suite = unittest.TestSuite()

for t in testmodules:
    try:
        # If the module defines a suite() function, call it to get the suite.
        mod = __import__(t, globals(), locals(), ['suite'])
        suitefn = getattr(mod, 'suite')
        suite.addTest(suitefn())
    except (ImportError, AttributeError):
        # else, just load all the test cases from the module.
        suite.addTest(unittest.defaultTestLoader.loadTestsFromName(t))

unittest.TextTestRunner().run(suite)
50 голосов
/ 05 ноября 2016

Теперь это возможно прямо из unittest: unittest.TestLoader.discover .

import unittest
loader = unittest.TestLoader()
start_dir = 'path/to/your/test/files'
suite = loader.discover(start_dir)

runner = unittest.TextTestRunner()
runner.run(suite)
41 голосов
/ 02 мая 2017

В Python 3, если вы используете unittest.TestCase:

  • В вашем каталоге test должен быть пустой (или другой) файл __init__.py ( должен иметь имя test/)
  • Ваши тестовые файлы внутри test/ соответствуют шаблону test_*.py. Они могут находиться внутри подкаталога под test/, и эти подкаталоги могут быть названы как угодно.

Затем вы можете запустить все тесты с помощью:

python -m unittest

Готово! Решение менее 100 строк. Надеюсь, другой начинающий питон сэкономит время, найдя это.

30 голосов
/ 14 ноября 2009

Ну, немного изучив приведенный выше код (в частности, используя TextTestRunner и defaultTestLoader), я смог довольно близко подойти. В конце концов я исправил свой код, просто передав все тестовые наборы одному конструктору наборов, а не добавляя их «вручную», что решило другие мои проблемы. Итак, вот мое решение.

import glob
import unittest

test_files = glob.glob('test_*.py')
module_strings = [test_file[0:len(test_file)-3] for test_file in test_files]
suites = [unittest.defaultTestLoader.loadTestsFromName(test_file) for test_file in module_strings]
test_suite = unittest.TestSuite(suites)
test_runner = unittest.TextTestRunner().run(test_suite)

Да, вероятно, проще просто использовать нос, чем делать это, но это не главное.

23 голосов
/ 31 июля 2011

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

from unittest import TestLoader, TextTestRunner, TestSuite
from uclid.test.test_symbols import TestSymbols
from uclid.test.test_patterns import TestPatterns

if __name__ == "__main__":

    loader = TestLoader()
    tests = [
        loader.loadTestsFromTestCase(test)
        for test in (TestSymbols, TestPatterns)
    ]
    suite = TestSuite(tests)

    runner = TextTestRunner(verbosity=2)
    runner.run(suite)

, где uclid - мой проект, а TestSymbols и TestPatterns - подклассы TestCase.

13 голосов
/ 16 февраля 2012

Я использовал метод discover и перегрузку load_tests для достижения этого результата в (как мне кажется, минимальном количестве) строк кода:

def load_tests(loader, tests, pattern):
''' Discover and load all unit tests in all files named ``*_test.py`` in ``./src/``
'''
    suite = TestSuite()
    for all_test_suite in unittest.defaultTestLoader.discover('src', pattern='*_tests.py'):
        for test_suite in all_test_suite:
            suite.addTests(test_suite)
    return suite

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

Казнь на пятерки что-то вроде

Ran 27 tests in 0.187s
OK
7 голосов
/ 05 ноября 2013

Я пробовал разные подходы, но все они кажутся ошибочными, или мне приходится придумывать какой-то код, это раздражает. Но в Linux есть удобный способ - просто найти каждый тест по определенному шаблону и затем вызывать их один за другим.

find . -name 'Test*py' -exec python '{}' \;

и самое главное, это определенно работает.

6 голосов
/ 08 августа 2015

В случае упакованной библиотеки или приложения вы не хотите этого делать. setuptools сделает это за вас .

Чтобы использовать эту команду, тесты вашего проекта должны быть заключены в набор тестов unittest с помощью функции, класса или метода TestCase, либо модуля или пакета, содержащих классы TestCase. Если названный набор является модулем, и модуль имеет функцию additional_tests(), он вызывается, и результат (который должен быть unittest.TestSuite) добавляется к выполняемым тестам. Если названный набор является пакетом, любые субмодули и подпакеты рекурсивно добавляются в общий набор тестов .

Просто скажите, где находится ваш корневой тестовый пакет, например:

setup(
    # ...
    test_suite = 'somepkg.test'
)

и запустить python setup.py test.

Обнаружение на основе файлов может быть проблематичным в Python 3, если только вы не избегаете относительного импорта в свой набор тестов, потому что discover использует импорт файлов. Хотя он поддерживает необязательный top_level_dir, но у меня были бесконечные ошибки рекурсии. Таким образом, простое решение для неупакованного кода - поместить следующее в __init__.py вашего тестового пакета (см. load_tests Protocol ).

import unittest

from . import foo, bar


def load_tests(loader, tests, pattern):
    suite = unittest.TestSuite()
    suite.addTests(loader.loadTestsFromModule(foo))
    suite.addTests(loader.loadTestsFromModule(bar))

    return suite
4 голосов
/ 22 июля 2014

Я использую PyDev / LiClipse и не совсем понял, как запустить все тесты сразу из графического интерфейса. (редактировать: вы щелкаете правой кнопкой мыши корневую тестовую папку и выбираете Run as -> Python unit-test

Это мой текущий обходной путь:

import unittest

def load_tests(loader, tests, pattern):
    return loader.discover('.')

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

Я поместил этот код в модуль с именем all в моей тестовой директории. Если я запускаю этот модуль как юнит-тест из LiClipse, тогда все тесты запускаются. Если я попрошу повторить только определенные или неудачные тесты, то будут запущены только эти тесты. Он также не мешает моему тестировщику командной строки (тесты на нос) - он игнорируется.

Возможно, вам потребуется изменить аргументы на discover в зависимости от настроек вашего проекта.

...