Запуск одного теста из unittest.TestCase через командную строку

257

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

Один «рамочный» класс ourtcfw.py:

import unittest

class OurTcFw(unittest.TestCase):
    def setUp:
        # something

    # other stuff that we want to use everywhere

и много тестов, таких как testMyCase.py:

import localweather

class MyCase(OurTcFw):

    def testItIsSunny(self):
        self.assertTrue(localweather.sunny)

    def testItIsHot(self):
        self.assertTrue(localweather.temperature > 20)

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

Когда я пишу новый тестовый код и хочу его часто запускать и экономить время, я делаю то, что ставлю «__» перед всеми остальными тестами. Но это громоздко, отвлекает меня от кода, который я пишу, и создаваемый им шум коммитов просто раздражает.

Например, при внесении изменений в testItIsHot(), я хочу быть в состоянии сделать это:

$ python testMyCase.py testItIsHot

и unittestпобежал только testItIsHot()

Как я могу этого достичь?

Я пытался переписать if __name__ == "__main__":часть, но так как я новичок в Python, я чувствую себя потерянным и продолжаю врезаться во все остальное, кроме методов.

Алоис Махдал
источник

Ответы:

311

Это работает, как вы предлагаете - вам просто нужно также указать имя класса:

python testMyCase.py MyCase.testItIsHot
phihag
источник
2
Боже мой! Поскольку тесты должны выполняться на python2.6 (99% времени я могу тестировать сами тесты с помощью python2.7), я смотрел документ 2.6.8 и так много пропустил! :-)
Алоис Махдал
1
Просто заметил, что это работает, только если метод называется «test *», поэтому, к сожалению, его нельзя использовать для запуска теста, который «отключен» переименованием
Alois Mahdal
4
Не работает для тестов в подкаталоге - самый распространенный случай в зрелой программе на Python.
Том Свирли,
4
@TomSwirly Не могу проверить сейчас, но я думаю, что вы можете сделать это путем создания (пустого) __init__.pyвнутри этой директории (и подкаталогов, если таковые имеются) и вызова eg. python test/testMyCase.py test.MyCase.testItIsHot,
Алоис Махдал
1
Ничего не происходит, когда я делаю это. Я нашел обходные пути, но я надеялся, что этот метод будет работать для меня.
Джо Флэк,
153

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

Вы также можете использовать следующий формат команды:

python -m unittest mypkg.tests.test_module.TestClass.test_method
# In your case, this would be:
python -m unittest testMyCase.MyCase.testItIsHot

Документация Python3 для этого: https://docs.python.org/3/library/unittest.html#command-line-interface

Аджай М
источник
Это так неловко в стиле Java. "long_module_name.SameLongNameAsAClass.test_long_name_beginning_with_test_as_a_convention" ... лучше надеяться, что вы не модульные в комплекты, как здравомыслящий человек, который проверяет их код.
Джошуа
69

Это может работать хорошо, как вы думаете

python testMyCase.py MyCase.testItIsHot

И есть еще один способ просто проверить testItIsHot:

    suite = unittest.TestSuite()
    suite.addTest(MyCase("testItIsHot"))
    runner = unittest.TextTestRunner()
    runner.run(suite)
Yarkee
источник
11
Вторая часть этого ответа показалась мне чрезвычайно полезной: я пишу тесты в Eclipse + PyDev и не хочу переключаться на командную строку!
Джованни ди Милия
25

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

python3 -m unittest -h

[...]

Examples:
  python3 -m unittest test_module               - run tests from test_module
  python3 -m unittest module.TestClass          - run tests from module.TestClass
  python3 -m unittest module.Class.test_method  - run specified test method

Это не требует, чтобы вы определили unittest.main()поведение вашего модуля по умолчанию.

skqr
источник
2
+1 и поскольку терминология может вводить в заблуждение, если вы плохо знакомы с языком (и usageдаже странно несовместимы): выполнение python -m unittest module_test.TestClass.test_methodпредполагает наличие файла module_test.py(запускается из текущего каталога; и не требуется); и содержит, который содержит (это также работает для меня на Python 2.7.13)__init.py__module_test.pyclass TestClass(unittest.TestCase)...def test_method(self,...)
Майкл
11

Может быть, это будет полезно для кого-то. Если вы хотите запускать только тесты из определенного класса:

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

Это работает для меня в Python 3.6

Богдана
источник
3

Вдохновленный @yarkee, я объединил его с некоторым кодом, который я уже получил. Вы также можете вызвать это из другого скрипта, просто вызвав функцию run_unit_tests()без необходимости использования командной строки, или просто вызвав ее из командной строки с помощью python3 my_test_file.py.

import my_test_file
my_test_file.run_unit_tests()

К сожалению, это работает только для Python 3.3или выше:

import unittest

class LineBalancingUnitTests(unittest.TestCase):

    @classmethod
    def setUp(self):
        self.maxDiff = None

    def test_it_is_sunny(self):
        self.assertTrue("a" == "a")

    def test_it_is_hot(self):
        self.assertTrue("a" != "b")

Код бегуна:

#! /usr/bin/env python3
# -*- coding: utf-8 -*-
import unittest
from .somewhere import LineBalancingUnitTests

def create_suite(classes, unit_tests_to_run):
    suite = unittest.TestSuite()
    unit_tests_to_run_count = len( unit_tests_to_run )

    for _class in classes:
        _object = _class()
        for function_name in dir( _object ):
            if function_name.lower().startswith( "test" ):
                if unit_tests_to_run_count > 0 \
                        and function_name not in unit_tests_to_run:
                    continue
                suite.addTest( _class( function_name ) )
    return suite

def run_unit_tests():
    runner = unittest.TextTestRunner()
    classes =  [
        LineBalancingUnitTests,
    ]

    # Comment all the tests names on this list, to run all Unit Tests
    unit_tests_to_run =  [
        "test_it_is_sunny",
        # "test_it_is_hot",
    ]
    runner.run( create_suite( classes, unit_tests_to_run ) )

if __name__ == "__main__":
    print( "\n\n" )
    run_unit_tests()

Немного отредактировав код, вы можете передать массив со всеми модульными тестами, которые вы хотели бы вызвать:

...
def run_unit_tests(unit_tests_to_run):
    runner = unittest.TextTestRunner()

    classes = \
    [
        LineBalancingUnitTests,
    ]

    runner.run( suite( classes, unit_tests_to_run ) )
...

И еще один файл:

import my_test_file

# Comment all the tests names on this list, to run all Unit Tests
unit_tests_to_run = \
[
    "test_it_is_sunny",
    # "test_it_is_hot",
]

my_test_file.run_unit_tests( unit_tests_to_run )

Кроме того, вы можете использовать https://docs.python.org/3/library/unittest.html#load-tests-protocol и определить следующий метод для вашего тестового модуля / файла:

def load_tests(loader, standard_tests, pattern):
    suite = unittest.TestSuite()

    # To add a single test from this file
    suite.addTest( LineBalancingUnitTests( 'test_it_is_sunny' ) )

    # To add a single test class from this file
    suite.addTests( unittest.TestLoader().loadTestsFromTestCase( LineBalancingUnitTests ) )

    return suite

Если вы хотите ограничить выполнение одним тестовым файлом, вам просто нужно установить шаблон обнаружения теста на единственный файл, в котором вы определили load_tests()функцию.

#! /usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import sys
import unittest

test_pattern = 'mytest/module/name.py'
PACKAGE_ROOT_DIRECTORY = os.path.dirname( os.path.realpath( __file__ ) )

loader = unittest.TestLoader()
start_dir = os.path.join( PACKAGE_ROOT_DIRECTORY, 'testing' )

suite = loader.discover( start_dir, test_pattern )
runner = unittest.TextTestRunner( verbosity=2 )
results = runner.run( suite )

print( "results: %s" % results )
print( "results.wasSuccessful: %s" % results.wasSuccessful() )

sys.exit( not results.wasSuccessful() )

Ссылки:

  1. Проблема с sys.argv [1], когда модуль unittest находится в скрипте
  2. Есть ли способ перебрать и выполнить все функции в классе Python?
  3. цикл по всем переменным-членам класса в Python

В качестве альтернативы последнему примеру основной программы, я прочитал следующий вариант после прочтения unittest.main()реализации метода:

  1. https://github.com/python/cpython/blob/master/Lib/unittest/main.py#L65
#! /usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import sys
import unittest

PACKAGE_ROOT_DIRECTORY = os.path.dirname( os.path.realpath( __file__ ) )
start_dir = os.path.join( PACKAGE_ROOT_DIRECTORY, 'testing' )

from testing_package import main_unit_tests_module
testNames = ["TestCaseClassName.test_nameHelloWorld"]

loader = unittest.TestLoader()
suite = loader.loadTestsFromNames( testNames, main_unit_tests_module )

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

print( "results: %s" % results )
print( "results.wasSuccessful: %s" % results.wasSuccessful() )
sys.exit( not results.wasSuccessful() )
пользователь
источник
3

TL; DR : Это очень вероятно сработает:

python mypkg/tests/test_module.py MyCase.testItIsHot

Объяснение :

  • Удобный способ

    python mypkg/tests/test_module.py MyCase.testItIsHot

    будет работать НО его невысказанное предположение, что у вас уже есть этот обычный фрагмент кода внутри (обычно в конце) вашего тестового файла.

    if __name__ == "__main__":
        unittest.main()
  • Неудобный способ

    python -m unittest mypkg.tests.test_module.TestClass.test_method

    будет всегда работать, не требуя, чтобы у вас был этот if __name__ == "__main__": unittest.main()фрагмент кода в вашем исходном файле теста.

Так почему 2-й метод считается неудобным? Потому что было бы больно (_ вставьте здесь одну из частей вашего тела _), чтобы набрать этот длинный путь, разделенный точками, вручную. В первом методе, mypkg/tests/test_module.pyчасть может быть автоматически завершена, либо современной оболочкой, либо вашим редактором.

PS: Если вы думали, что часть тела находится где-то ниже вашей талии, вы подлинный человек. :-) Я имею в виду сказать "сустав пальцев". Слишком много печатать будет плохо для ваших суставов. ;-)

RayLuo
источник