Python unittest: Wie führe ich nur einen Teil einer Testdatei aus?

75

Ich habe eine Testdatei, die Tests enthält, die ziemlich viel Zeit in Anspruch nehmen (sie senden Berechnungen an einen Cluster und warten auf das Ergebnis). Alle diese sind in einer bestimmten TestCase-Klasse.

Da sie Zeit brauchen und außerdem wahrscheinlich nicht brechen, möchte ich entscheiden können, ob diese Teilmenge von Tests ausgeführt wird oder nicht (der beste Weg wäre ein Befehlszeilenargument, dh " ./tests.py --offline" oder so so), so dass ich die meisten Tests oft und schnell durchführen konnte und das ganze Set ab und zu, wenn ich Zeit habe.

Im Moment starte ich nur unittest.main()die Tests.

Vielen Dank.

Gohu
quelle

Antworten:

52

Der Standard unittest.main()verwendet den Standard-Testloader, um aus dem Modul, in dem main ausgeführt wird, eine TestSuite zu erstellen.

Sie müssen dieses Standardverhalten nicht verwenden.

Sie können beispielsweise drei unittest.TestSuite- Instanzen erstellen .

  1. Die "schnelle" Teilmenge.

    fast = TestSuite()
    fast.addTests( TestFastThis )
    fast.addTests( TestFastThat )
    
  2. Die "langsame" Teilmenge.

    slow = TestSuite()
    slow.addTests( TestSlowAnother )
    slow.addTests( TestSlowSomeMore )
    
  3. Das "ganze" Set.

    alltests = unittest.TestSuite([fast, slow])
    

Beachten Sie, dass ich die TestCase-Namen so angepasst habe, dass sie Fast vs. Slow anzeigen. Sie können unittest.TestLoader in Unterklassen unterteilen, um die Namen von Klassen zu analysieren und mehrere Loader zu erstellen.

Dann kann Ihr Hauptprogramm Befehlszeilenargumente mit optparse oder argparse (verfügbar seit 2.7 oder 3.2) analysieren, um auszuwählen, welche Suite Sie ausführen möchten, schnell, langsam oder alle.

Oder Sie können darauf vertrauen, dass dies sys.argv[1]einer von drei Werten ist, und so etwas Einfaches verwenden

if __name__ == "__main__":
    suite = eval(sys.argv[1])  # Be careful with this line!
    unittest.TextTestRunner().run(suite)
S.Lott
quelle
schön, wenn es nur so einfach wäre in C ++ Land für meine Algorithmus-Stresstests :)
Matt Joiner
@MattJoiner: Ich kenne einen Freund, der Python verwendet und ctypesUnit-Tests für C / C ++ - Code geschrieben und ausgeführt hat. Auf jeden Fall ist dies kein Thema für diese Frage.
Denilson Sá Maia
1
Ich musste den Code ändern, damit dies funktioniert. Hier ist, was für mich funktioniert hat: test_class = eval(sys.argv[1]) suite = unittest.TestLoader().loadTestsFromTestCase(test_class) unittest.TextTestRunner().run(suite)
Dirty Penguin
1
Kleine Änderung: Ich denke, es sollte addTest sein, nicht addTests. Dokumente sagen, dass addTests für iterierbare Tests ist, während addTest für das Hinzufügen einer TestCase-Klasse ist
Sam Bobel
84

Um nur einen bestimmten Test auszuführen, können Sie Folgendes verwenden:

$ python -m unittest test_module.TestClass.test_method

Weitere Informationen hier

Amit Kotlovski
quelle
1
Ich debugge meine Testfälle, daher ist diese Methode viel hilfreicher als die akzeptierte Antwort. Vielen Dank.
Luanjunyi
Dies funktioniert auch mit mehr als einem Test gleichzeitig. Stellen Sie einfach sicher, dass sie wie folgt durch Leerzeichen begrenzt sind : python -m unittest test_module.TestClass.test_method test_module.TestClass.test_method2. Selbst wenn Sie eine Handvoll verwandter Testfälle ausführen müssen, kann dies dennoch sehr nützlich sein.
Eestrada
13

Ich mache das mit einem einfachen skipIf:

import os

SLOW_TESTS = int(os.getenv('SLOW_TESTS', '0'))

@unittest.skipIf(not SLOW_TESTS, "slow")
class CheckMyFeature(unittest.TestCase):
    def runTest(self):

Auf diese Weise muss ich nur einen bereits vorhandenen Testfall mit dieser einzelnen Zeile dekorieren (es müssen keine Testsuiten oder ähnliches erstellt werden, nur diese eine os.getenv()Anrufzeile am Anfang meiner Komponententestdatei), und dieser Test wird standardmäßig übersprungen.

Wenn ich es ausführen möchte, obwohl es langsam ist, rufe ich mein Skript einfach so auf:

SLOW_TESTS=1 python -m unittest …
Alfe
quelle
12

Tatsächlich kann man die Namen des Testfalls als sys.argv übergeben und nur diese Fälle werden getestet.

Angenommen, Sie haben

class TestAccount(unittest.TestCase):
    ...

class TestCustomer(unittest.TestCase):
    ...

class TestShipping(unittest.TestCase):
    ...

account = TestAccount
customer = TestCustomer
shipping = TestShipping

Du kannst anrufen

python test.py account

nur Account-Tests zu haben oder sogar

$ python test.py account customer

beide Fälle testen lassen

Tzury Bar Yochay
quelle
2
Funktioniert für mich in Python 2.7.11 und 3.5.1. Die Namen sind die Attribute, die auf dem Modul verfügbar sind. account = TestAccountwird nicht benötigt, können Sie auch verwenden python test.py TestAccount.
Rob W
So erweitern Sie meinen vorherigen Kommentar (und geben das Offensichtliche an): Dieser Befehlszeilenparameter funktioniert, sofern er unittest.main()aufgerufen wird. ZBif __name__ == '__main__': unittest.main()
Rob W
9

Sie haben grundsätzlich zwei Möglichkeiten:

  1. Definieren Sie Ihre eigene Testsuite für die Klasse
  2. Erstellen Sie Scheinklassen der Clusterverbindung, die die tatsächlichen Daten zurückgeben.

Ich bin ein starker Befürworter des zweiten Ansatzes; Ein Komponententest sollte nur eine Codeeinheit und keine komplexen Systeme (wie Datenbanken oder Cluster) testen. Aber ich verstehe, dass es nicht immer möglich ist; Manchmal ist das Erstellen von Modellen einfach zu teuer, oder das Ziel des Tests liegt wirklich im komplexen System.

Zurück zu Option (1) können Sie folgendermaßen vorgehen:

suite = unittest.TestSuite()
suite.addTest(MyUnitTestClass('quickRunningTest'))
suite.addTest(MyUnitTestClass('otherTest'))

und dann die Suite an den Testläufer weitergeben:

unittest.TextTestRunner().run(suite)

Weitere Informationen zur Python-Dokumentation: http://docs.python.org/library/unittest.html#testsuite-objects

rauben
quelle
Ja, ich kenne mich mit Scheinobjekten aus, aber ich denke, das wird zu kompliziert. Der Python stellt keine direkte Verbindung zum Cluster her, sondern durchläuft eine Reihe von Bash-Skripten, deren Verhalten ich testen muss. Also müsste ich "Mock-Skripte" erstellen, die sich bis auf den letzten Verbindungsteil genauso verhalten wie die echten, aber dann müsste ich die beiden gleichzeitig pflegen und sicherstellen, dass sie für was gleichwertig sind Ich möchte testen ... Vielen Dank für Ihre Antwort zu den Testsuiten. Ich habe die Antwort von S. Lott gewählt, weil sie etwas detaillierter ist, aber im Grunde das Gleiche.
Gohu
7

Da Sie verwenden unittest.main(), können Sie einfach ausführen python tests.py --help, um die Dokumentation zu erhalten:

Usage: tests.py [options] [test] [...]

Options:
  -h, --help       Show this message
  -v, --verbose    Verbose output
  -q, --quiet      Minimal output
  -f, --failfast   Stop on first failure
  -c, --catch      Catch control-C and display results
  -b, --buffer     Buffer stdout and stderr during test runs

Examples:
  tests.py                               - run default set of tests
  tests.py MyTestSuite                   - run suite 'MyTestSuite'
  tests.py MyTestCase.testSomething      - run MyTestCase.testSomething
  tests.py MyTestCase                    - run all 'test*' test methods
                                               in MyTestCase

Das heißt, Sie können es einfach tun

python tests.py TestClass.test_method
Thomas Ahle
quelle
2

Oder Sie können die unittest.SkipTest()Funktion nutzen. Fügen Sie skipOrRunTestIhrer Testklasse beispielsweise eine Methode wie folgt hinzu:

def skipOrRunTest(self,testType):
    #testsToRun = 'ALL'
    #testsToRun = 'testType1, testType2, testType3, testType4,...etc'
    #testsToRun = 'testType1'
    #testsToRun = 'testType2'
    #testsToRun = 'testType3'
    testsToRun = 'testType4'              
    if ((testsToRun == 'ALL') or (testType in testsToRun)):
        return True 
    else:
        print "SKIPPED TEST because:\n\t testSuite '" + testType  + "' NOT IN testsToRun['" + testsToRun + "']" 
        self.skipTest("skipppy!!!")

Fügen Sie dann am Anfang jedes Unit-Tests wie folgt einen Aufruf zu dieser skipOrRunTest-Methode hinzu:

def testType4(self):
    self.skipOrRunTest('testType4')
Joe
quelle
Sie könnten die Testdekoration überspringen, z. B. @ unittest2.skipUnless (runninglowtests (), "langsamer Test")
gaoithe
2

Ich habe eine andere Lösung gefunden, basierend darauf, wie der unittest.skipDekorateur arbeitet. Durch Einstellen von __unittest_skip__und __unittest_skip_why__.

Etikettenbasiert

Ich wollte ein Kennzeichnungssystem anzuwenden, um einige Tests zu beschriften quick, slow, glacier, memoryhog, cpuhog, core, und so weiter.

Führen Sie dann all 'quick' testsoder run everything except 'memoryhog' testsIhre grundlegende Whitelist- / Blacklist-Einrichtung aus

Implementierung

Ich habe dies in 2 Teilen implementiert:

  1. Fügen Sie zuerst Etiketten zu Tests hinzu (über einen benutzerdefinierten @testlabelKlassendekorateur).
  2. Benutzerdefiniert unittest.TestRunner, um zu identifizieren, welche Tests übersprungen werden sollen, und um den Inhalt der Testliste vor der Ausführung zu ändern.

Die Arbeitsimplementierung befindet sich in diesem Kern: https://gist.github.com/fragmuffin/a245f59bdcd457936c3b51aa2ebb3f6c

(Ein voll funktionsfähiges Beispiel war zu lang, um es hier zu platzieren)

Das Ergebnis ist ...

$ ./runtests.py --blacklist foo
test_foo (test_things.MyTest2) ... ok
test_bar (test_things.MyTest3) ... ok
test_one (test_things.MyTests1) ... skipped 'label exclusion'
test_two (test_things.MyTests1) ... skipped 'label exclusion'

----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK (skipped=2)

Alle MyTests1Klassentests werden übersprungen, da sie das fooEtikett haben.

--whitelist funktioniert auch

FraggaMuffin
quelle
1

Verwenden Sie einen speziellen Testrunner wie py.test, Nase oder möglicherweise sogar zope.testing. Sie alle verfügen über Befehlszeilenoptionen zur Auswahl von Tests.

Suchen Sie beispielsweise nach Nase: https://pypi.python.org/pypi/nose/1.3.0

Lennart Regebro
quelle
Vielen Dank für Ihre Antwort, aber ich denke, es ist ein bisschen übertrieben, deshalb habe ich mich für die TestSuites entschieden.
Gohu
Die URL für Nase 404s.
George Stocker
1
@ GeorgeStocker: Sie können Google nicht verwenden, um die neue URL zu finden?
Lennart Regebro
4
@LennartRegebro Ihre Antwort sollte eigenständig sein, ohne dass Links erforderlich sind, um sie zu vervollständigen. Links sollten ergänzende Informationen sein. So wie es aussieht, beantwortet Ihre Antwort die Frage nicht. Ganz zu schweigen vom nützlichen Teil der Antwort 404s. Siehe auch: meta.stackexchange.com/questions/8231/…
George Stocker
1
@GeorgeStocker: Es beantwortet die Frage auf nützliche Weise (und weist darauf hin, dass der Link zusätzliche Informationen enthält, wenn Sie diese Art von Funktionalität verwenden möchten, indem Sie ein Framework verwenden, das unittest erweitert). Ich habe den Link behoben.
Lennart Regebro
1

Ich habe versucht, @ slott zu antworten:

if __name__ == "__main__":
    suite = eval(sys.argv[1])  # Be careful with this line!
    unittest.TextTestRunner().run(suite)

Aber das gab mir den folgenden Fehler:

Traceback (most recent call last):
  File "functional_tests.py", line 178, in <module>
    unittest.TextTestRunner().run(suite)
  File "/usr/lib/python2.7/unittest/runner.py", line 151, in run
    test(result)
  File "/usr/lib/python2.7/unittest/case.py", line 188, in __init__
    testMethod = getattr(self, methodName)
TypeError: getattr(): attribute name must be string

Folgendes hat bei mir funktioniert:

if __name__ == "__main__":
    test_class = eval(sys.argv[1])
    suite = unittest.TestLoader().loadTestsFromTestCase(test_class)
    unittest.TextTestRunner().run(suite)
Schmutziger Pinguin
quelle
0

Ich habe eine andere Möglichkeit gefunden, die test_ * -Methoden auszuwählen, die ich nur ausführen möchte, indem ich ihnen ein Attribut hinzufüge. Grundsätzlich verwenden Sie eine Metaklasse, um die aufrufbaren Elemente innerhalb der TestCase-Klasse mit dem StepDebug-Attribut mit einem unittest.skip-Dekorator zu dekorieren. Weitere Infos zu

Überspringen aller Unit-Tests bis auf einen in Python mithilfe von Dekoratoren und Metaklassen

Ich weiß nicht, ob es eine bessere Lösung als die oben genannten ist. Ich biete sie nur als Option an.

Satrapes
quelle
0

Ich habe noch nie einen guten Weg gefunden, dies zu tun, also teile es hier.

Ziel: Stellen Sie eine Reihe von Testdateien zusammen, damit sie als Einheit ausgeführt werden können. Wir können jedoch weiterhin eine von ihnen auswählen, die für sich selbst ausgeführt werden soll.

Problem: Die Erkennungsmethode ermöglicht keine einfache Auswahl eines einzelnen Testfalls.

Design: siehe unten. Diese flacht den Namensraum so wählen kann durch Testcase Klassennamen, und lassen Sie den den „tests1.test_core“ prefix:

./run-tests TestCore.test_fmap

Code

  test_module_names = [
    'tests1.test_core',
    'tests2.test_other',
    'tests3.test_foo',
    ]

  loader = unittest.defaultTestLoader
  if args:
    alltests = unittest.TestSuite()
    for a in args:
      for m in test_module_names:
        try:
          alltests.addTest( loader.loadTestsFromName( m+'.'+a ) )
        except AttributeError as e:
          continue
  else:
    alltests = loader.loadTestsFromNames( test_module_names )

  runner = unittest.TextTestRunner( verbosity = opt.verbose )
  runner.run( alltests )
FDS
quelle
0

Dies ist das einzige, was für mich funktioniert hat.

if __name__ == '__main__':
unittest.main( argv=sys.argv, testRunner = unittest.TextTestRunner(verbosity=2))

Als ich es anrief, musste ich den Namen der Klasse und den Testnamen übergeben. Ein wenig unpraktisch, da ich keine Kombination aus Klasse und Testname auswendig gelernt habe.

python ./tests.py class_Name.test_30311

Durch Entfernen des Klassennamens und des Testnamens werden alle Tests in Ihrer Datei ausgeführt. Ich finde das VIEL einfacher zu handhaben als die eingebaute Methode, da ich meinen Befehl auf der CLI nicht wirklich ändere. Fügen Sie einfach den Parameter hinzu.

Viel Spaß, Keith

Keith
quelle
0

Ich habe einen Dekorator erstellt, mit dem Tests als langsame Tests markiert und mithilfe einer Umgebungsvariablen übersprungen werden können

from unittest import skip
import os

def slow_test(func):
    return skipIf('SKIP_SLOW_TESTS' in os.environ, 'Skipping slow test')(func)

Jetzt können Sie Ihre Tests wie folgt als langsam markieren:

@slow_test
def test_my_funky_thing():
    perform_test()

Überspringen Sie langsame Tests, indem Sie die SKIP_SLOW_TESTSUmgebungsvariable festlegen:

SKIP_SLOW_TESTS=1 python -m unittest
devsnd
quelle