Ausführen eines einzelnen Tests von unittest.TestCase über die Befehlszeile

256

In unserem Team definieren wir die meisten Testfälle folgendermaßen:

Eine "Framework" -Klasse ourtcfw.py:

import unittest

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

    # other stuff that we want to use everywhere

und viele Testfälle wie 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()

Wenn ich neuen Testcode schreibe und ihn häufig ausführen und Zeit sparen möchte, setze ich "__" vor alle anderen Tests. Aber es ist umständlich, lenkt mich von dem Code ab, den ich schreibe, und das dadurch verursachte Commit-Rauschen ist einfach ärgerlich.

Wenn testItIsHot()ich z. B. Änderungen an vornehme , möchte ich dies tun können:

$ python testMyCase.py testItIsHot

und sind nurunittest gelaufen testItIsHot()

Wie kann ich das erreichen?

Ich habe versucht, den if __name__ == "__main__":Teil neu zu schreiben , aber da ich neu in Python bin, fühle ich mich verloren und stoße immer wieder auf alles andere als die Methoden.

Alois Mahdal
quelle

Antworten:

311

Dies funktioniert wie von Ihnen vorgeschlagen - Sie müssen nur auch den Klassennamen angeben:

python testMyCase.py MyCase.testItIsHot
Phihag
quelle
2
Oh mein! Da die Tests auf Python2.6 ausgeführt werden sollen (99% der Zeit kann ich die Tests selbst mit Python2.7 testen), habe ich mir 2.6.8 doc angesehen und so viel verpasst! :-)
Alois Mahdal
1
Ich habe gerade bemerkt, dass dies nur funktioniert, wenn die Methode "test *" heißt.
Daher
4
Funktioniert nicht für Tests in einem Unterverzeichnis - der häufigste Fall in einem ausgereiften Python-Programm.
Tom Swirly
4
@ TomSwirly Kann jetzt nicht überprüfen, aber ich denke, Sie können dies tun, indem Sie (leer) __init__.pyin diesem Verzeichnis (und gegebenenfalls Unterverzeichnissen) erstellen und z. python test/testMyCase.py test.MyCase.testItIsHot.
Alois Mahdal
1
Wenn ich das mache, passiert nichts. Ich habe Problemumgehungen gefunden, aber ich hatte gehofft, dass diese Methode für mich funktionieren würde.
Joe Flack
152

Wenn Sie Ihre Testfälle organisieren, folgen Sie der gleichen Organisation wie der eigentliche Code und verwenden Sie auch relative Importe für Module im selben Paket

Sie können auch das folgende Befehlsformat verwenden:

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

Python3-Dokumentation hierzu: https://docs.python.org/3/library/unittest.html#command-line-interface

Ajay M.
quelle
Das ist so klobig Java-artig. "long_module_name.SameLongNameAsAClass.test_long_name_beginning_with_test_as_a_convention" ... hoffe besser, dass Sie nicht wie eine vernünftige Person, die ihren Code testet, in Suiten modularisiert haben.
Joshua Detwiler
69

Es kann gut funktionieren, wie Sie vermuten

python testMyCase.py MyCase.testItIsHot

Und es gibt noch eine andere Möglichkeit, nur zu testen testItIsHot:

    suite = unittest.TestSuite()
    suite.addTest(MyCase("testItIsHot"))
    runner = unittest.TextTestRunner()
    runner.run(suite)
Yarkee
quelle
11
Ich fand den zweiten Teil dieser Antwort äußerst hilfreich: Ich schreibe Tests in Eclipse + PyDev und möchte nicht zur Befehlszeile wechseln!
Giovanni Di Milia
25

Wenn Sie sich die Hilfe des unittest-Moduls ansehen, werden verschiedene Kombinationen beschrieben, mit denen Sie Testfallklassen aus einem Modul und Testmethoden aus einer Testfallklasse ausführen können.

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

Es ist nicht erforderlich, dass Sie a unittest.main()als Standardverhalten Ihres Moduls definieren.

skqr
quelle
2
+1 und da die Terminologie verwirrend sein kann, wenn sie neu in einer Sprache ist (und die usagesogar seltsamerweise inkonsistent ist): Ausführen python -m unittest module_test.TestClass.test_methodsetzt eine Datei voraus module_test.py(Ausführen aus dem aktuellen Verzeichnis; und __init.py__ist nicht erforderlich); und module_test.pyenthält, class TestClass(unittest.TestCase)...die enthält def test_method(self,...)(dies funktioniert auch für mich auf Python 2.7.13)
Michael
10

Vielleicht ist es für jemanden hilfreich. Wenn Sie nur Tests einer bestimmten Klasse ausführen möchten:

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

Es funktioniert für mich in Python 3.6

Bohdan
quelle
3

Inspiriert von @yarkee habe ich es mit einem Teil des Codes kombiniert, den ich bereits erhalten habe. Sie können dies auch von einem anderen Skript aus aufrufen, indem Sie die Funktion aufrufen, run_unit_tests()ohne die Befehlszeile verwenden zu müssen, oder sie einfach über die Befehlszeile mit aufrufen python3 my_test_file.py.

import my_test_file
my_test_file.run_unit_tests()

Leider funktioniert dies nur für Python 3.3oder überlegen:

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

Runner-Code:

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

Wenn Sie den Code ein wenig bearbeiten, können Sie ein Array mit allen Unit-Tests übergeben, die Sie aufrufen möchten:

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

    classes = \
    [
        LineBalancingUnitTests,
    ]

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

Und noch eine Datei:

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 )

Alternativ können Sie https://docs.python.org/3/library/unittest.html#load-tests-protocol verwenden und die folgende Methode für Ihr Testmodul / Ihre Testdatei definieren:

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

Wenn Sie die Ausführung auf eine einzelne Testdatei beschränken möchten, müssen Sie nur das Testerkennungsmuster auf die einzige Datei festlegen, in der Sie die load_tests()Funktion definiert haben .

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

Verweise:

  1. Problem mit sys.argv [1], wenn sich das unittest-Modul in einem Skript befindet
  2. Gibt es eine Möglichkeit, alle Funktionen einer Python-Klasse zu durchlaufen und auszuführen?
  3. Durchlaufen aller Mitgliedsvariablen einer Klasse in Python

Alternativ zum letzten Hauptprogrammbeispiel habe ich nach dem Lesen der unittest.main()Methodenimplementierung die folgende Variante gefunden :

  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() )
Benutzer
quelle
3

TL; DR : Dies würde sehr wahrscheinlich funktionieren:

python mypkg/tests/test_module.py MyCase.testItIsHot

Die Erklärung :

  • Der bequeme Weg

    python mypkg/tests/test_module.py MyCase.testItIsHot

    würde funktionieren, ABER seine unausgesprochene Annahme ist, dass Sie dieses herkömmliche Code-Snippet bereits (normalerweise am Ende) Ihrer Testdatei haben.

    if __name__ == "__main__":
        unittest.main()
  • Der unbequeme Weg

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

    würde immer funktionieren, ohne dass Sie dieses if __name__ == "__main__": unittest.main()Code-Snippet in Ihrer Testquelldatei haben müssen.

Warum wird die zweite Methode als unpraktisch angesehen? Weil es ein Schmerz wäre, wenn Sie diesen langen, punktbegrenzten Pfad von Hand eingeben (_ fügen Sie hier einen Ihrer Körperteile ein _). Während in der 1. Methode diemypkg/tests/test_module.py Teil automatisch vervollständigt werden, entweder durch eine moderne Shell oder durch Ihren Editor.

PS: Wenn Sie dachten, dass sich der Körperteil irgendwo unterhalb Ihrer Taille befindet, sind Sie eine authentische Person. :-) Ich will "Fingergelenk" sagen. Zu viel Tippen wäre schlecht für Ihre Gelenke. ;-);

RayLuo
quelle