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


256

У нашій команді ми визначаємо більшість тестових випадків на зразок цього:

Один клас "рамки" 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

2
О Боже! Оскільки тести потрібно проводити на python2.6 (у 99% часу я можу тестувати самі тести з python2.7), я дивився на 2.6.8 док і пропустив стільки! :-)
Алоїс Магдал

1
Щойно зауважив, що це працює лише в тому випадку, якщо метод називається "test *", тому, на жаль, його не можна використовувати, щоб періодично запускати тест, який "відключений" за допомогою перейменування
Alois Mahdal

4
Не працює для тестів у підкаталозі - найпоширеніший випадок у зрілій програмі Python.
Tom Swirly

4
@TomSwirly Не можу перевірити зараз, але я думаю, що ви можете це зробити, створивши (порожній) __init__.pyвсередині цієї директрорії (та підкаталоги, якщо такі є) та зателефонувавши, наприклад. python test/testMyCase.py test.MyCase.testItIsHot.
Алоїс Магдал

1
Нічого не відбувається, коли я це роблю. Я знайшов обхідні шляхи, але сподівався, що цей метод допоможе мені.
Джо Флак

152

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

Ви також можете використовувати такий формат команд:

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

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()як поведінку вашого модуля за замовчуванням.


2
+1 і оскільки термінологія може ввести в оману , якщо новий на мову (і usageнавіть дивно непослідовним): біг python -m unittest module_test.TestClass.test_methodпередбачає файл module_test.py(запускається з поточного каталогу, і __init.py__це НЕ потрібно); і module_test.pyмістить class TestClass(unittest.TestCase)...який містить def test_method(self,...)(це також працює для мене на python 2.7.13)
michael

10

Можливо, комусь це буде корисно. Якщо ви хочете запускати лише тести певного класу:

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-й метод вважається незручним? Тому що це буде біль у (_ вставте сюди свою частину тіла _) набрати вручну довгий шлях, розмежований крапкою. Перебуваючи в 1-му методі, тоmypkg/tests/test_module.py деталь може бути автоматично заповнена або сучасним оболонкою, або вашим редактором.

PS: Якщо ви думали, що частина тіла десь нижче вашої талії, ви справжня людина. :-) Я маю на увазі сказати "суглоб пальця". Занадто багато введення було б погано для ваших суглобів. ;-)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.