Как запустить все модульные тесты 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 ]

2 голосов
/ 03 июля 2014

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

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

1 голос
/ 13 августа 2018

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

ВСЕ ИСПЫТАНИЯ, независимые $ PWD

модуль Python unittest чувствителен к вашему текущему каталогу, если вы не укажете ему, где (используя опцию 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, чтобы нагружать ваш пакет / накладные расходы памяти во время производства.

0 голосов
/ 24 мая 2015

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

#!/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())

Ради простоты прошу прощения за мои не PEP8 стандарты кодирования.

Затем вы можете создать класс 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
0 голосов
/ 15 сентября 2014

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

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

...