Как мне запустить все модульные тесты Python в каталоге?

StackOverflow https://stackoverflow.com/questions/1732438

Вопрос

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

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

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, но это прогресс.Но как мне передать и отобразить результат в main?Или как мне в принципе заставить его работать, чтобы я мог просто запустить этот файл и при этом запустить все модульные тесты в этом каталоге?

Это было полезно?

Решение

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

Обновленный:

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

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)

Другие советы

С 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: unittest.Загрузчик тестов.откройте для себя.

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

runner = unittest.TextTestRunner()
runner.run(suite)

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

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

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

python -m unittest

Сделано!Решение размером менее 100 строк.Надеюсь, еще один новичок в python сэкономит время, найдя это.

Хорошо, немного изучив приведенный выше код (в частности, используя 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)

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

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

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.

Я использовал 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

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

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

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

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

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

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

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

import unittest

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

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

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

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

Основываясь на ответе Стивен Кейгл Я добавил поддержку вложенных тестовых модулей.

import fnmatch
import os
import unittest

def all_test_modules(root_dir, pattern):
    test_file_names = all_files_in(root_dir, pattern)
    return [path_to_module(str) for str in test_file_names]

def all_files_in(root_dir, pattern):
    matches = []

    for root, dirnames, filenames in os.walk(root_dir):
        for filename in fnmatch.filter(filenames, pattern):
            matches.append(os.path.join(root, filename))

    return matches

def path_to_module(py_file):
    return strip_leading_dots( \
        replace_slash_by_dot(  \
            strip_extension(py_file)))

def strip_extension(py_file):
    return py_file[0:len(py_file) - len('.py')]

def replace_slash_by_dot(str):
    return str.replace('\\', '.').replace('/', '.')

def strip_leading_dots(str):
    while str.startswith('.'):
       str = str[1:len(str)]
    return str

module_names = all_test_modules('.', '*Tests.py')
suites = [unittest.defaultTestLoader.loadTestsFromName(mname) for mname 
    in module_names]

testSuite = unittest.TestSuite(suites)
runner = unittest.TextTestRunner(verbosity=1)
runner.run(testSuite)

Код выполняет поиск во всех подкаталогах . для *Tests.py файлы, которые затем загружаются.Он ожидает, что каждый *Tests.py содержать один класс *Tests(unittest.TestCase) который загружается по очереди и выполняется один за другим.

Это работает с произвольной глубокой вложенностью каталогов / модулей, но каждый промежуточный каталог должен содержать пустой __init__.py по крайней мере, файл.Это позволяет тесту загружать вложенные модули, заменяя косую черту (или обратную косую черту) точками (см. replace_slash_by_dot).

Поскольку тестовое обнаружение кажется законченной темой, существует некоторая специальная платформа для тестового обнаружения :

Подробнее читайте здесь : https://wiki .python.org/moin/PythonTestingToolsTaxonomy

Этот скрипт BASH выполнит тестовый каталог python unittest из ЛЮБОГО места файловой системы, независимо от того, в каком рабочем каталоге вы находитесь:его рабочий каталог всегда находится там, где это test каталог находится.

ВСЕ ТЕСТЫ, независимые $PWD

модуль unittest Python чувствителен к вашему текущему каталогу, если вы не укажете ему, где (используя discover -s вариант).

Это полезно при пребывании в ./src или ./example рабочий каталог, и вам нужен быстрый общий модульный тест:

#!/bin/bash
this_program="$0"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"

python -m unittest discover -s "$readlink"/test -v

ИЗБРАННЫЕ ТЕСТЫ, независимые $PWD

Я называю этот служебный файл: runone.py и используйте это вот так:

runone.py <test-python-filename-minus-dot-py-fileextension>
#!/bin/bash
this_program="$0"
dirname="`dirname $this_program`"
readlink="`readlink -e $dirname`"

(cd "$dirname"/test; python -m unittest $1)

Нет необходимости в test/__init__.py файл, который будет обременять ваш пакет / нагрузку на память во время производства.

Вот мой подход к созданию обертка для запуска тестов из командной строки:

#!/usr/bin/env python3
import os, sys, unittest, argparse, inspect, logging

if __name__ == '__main__':
    # Parse arguments.
    parser = argparse.ArgumentParser(add_help=False)
    parser.add_argument("-?", "--help",     action="help",                        help="show this help message and exit" )
    parser.add_argument("-v", "--verbose",  action="store_true", dest="verbose",  help="increase output verbosity" )
    parser.add_argument("-d", "--debug",    action="store_true", dest="debug",    help="show debug messages" )
    parser.add_argument("-h", "--host",     action="store",      dest="host",     help="Destination host" )
    parser.add_argument("-b", "--browser",  action="store",      dest="browser",  help="Browser driver.", choices=["Firefox", "Chrome", "IE", "Opera", "PhantomJS"] )
    parser.add_argument("-r", "--reports-dir", action="store",   dest="dir",      help="Directory to save screenshots.", default="reports")
    parser.add_argument('files', nargs='*')
    args = parser.parse_args()

    # Load files from the arguments.
    for filename in args.files:
        exec(open(filename).read())

    # See: http://codereview.stackexchange.com/q/88655/15346
    def make_suite(tc_class):
        testloader = unittest.TestLoader()
        testnames = testloader.getTestCaseNames(tc_class)
        suite = unittest.TestSuite()
        for name in testnames:
            suite.addTest(tc_class(name, cargs=args))
        return suite

    # Add all tests.
    alltests = unittest.TestSuite()
    for name, obj in inspect.getmembers(sys.modules[__name__]):
        if inspect.isclass(obj) and name.startswith("FooTest"):
            alltests.addTest(make_suite(obj))

    # Set-up logger
    verbose = bool(os.environ.get('VERBOSE', args.verbose))
    debug   = bool(os.environ.get('DEBUG', args.debug))
    if verbose or debug:
        logging.basicConfig( stream=sys.stdout )
        root = logging.getLogger()
        root.setLevel(logging.INFO if verbose else logging.DEBUG)
        ch = logging.StreamHandler(sys.stdout)
        ch.setLevel(logging.INFO if verbose else logging.DEBUG)
        ch.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(name)s: %(message)s'))
        root.addHandler(ch)
    else:
        logging.basicConfig(stream=sys.stderr)

    # Run tests.
    result = unittest.TextTestRunner(verbosity=2).run(alltests)
    sys.exit(not result.wasSuccessful())

Для простоты, пожалуйста, извините мое не-ПЕП8 стандарты кодирования.

Затем вы можете создать класс BaseTest для общих компонентов для всех ваших тестов, чтобы каждый ваш тест выглядел просто как:

from BaseTest import BaseTest
class FooTestPagesBasic(BaseTest):
    def test_foo(self):
        driver = self.driver
        driver.get(self.base_url + "/")

Для запуска вы просто указываете тесты как часть аргументов командной строки, например:

./run_tests.py -h http://example.com/ tests/**/*.py
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top