Fortsetzung von Pythons Unittest, wenn eine Behauptung fehlschlägt

84

BEARBEITEN: wechselte zu einem besseren Beispiel und stellte klar, warum dies ein echtes Problem ist.

Ich möchte Komponententests in Python schreiben, die weiterhin ausgeführt werden, wenn eine Zusicherung fehlschlägt, damit ich mehrere Fehler in einem einzigen Test sehen kann. Zum Beispiel:

class Car(object):
  def __init__(self, make, model):
    self.make = make
    self.model = make  # Copy and paste error: should be model.
    self.has_seats = True
    self.wheel_count = 3  # Typo: should be 4.

class CarTest(unittest.TestCase):
  def test_init(self):
    make = "Ford"
    model = "Model T"
    car = Car(make=make, model=model)
    self.assertEqual(car.make, make)
    self.assertEqual(car.model, model)  # Failure!
    self.assertTrue(car.has_seats)
    self.assertEqual(car.wheel_count, 4)  # Failure!

Hier besteht der Zweck des Tests darin, sicherzustellen, dass das Auto __init__seine Felder korrekt einstellt. Ich könnte es in vier Methoden aufteilen (und das ist oft eine großartige Idee), aber in diesem Fall denke ich, dass es besser lesbar ist, es als einzelne Methode beizubehalten, die ein einzelnes Konzept testet ("das Objekt ist korrekt initialisiert").

Wenn wir davon ausgehen, dass es hier am besten ist, die Methode nicht aufzubrechen, habe ich ein neues Problem: Ich kann nicht alle Fehler gleichzeitig sehen. Wenn ich den modelFehler behebe und den Test erneut ausführe, wird der wheel_countFehler angezeigt. Es würde mir Zeit sparen, beide Fehler zu sehen, wenn ich den Test zum ersten Mal ausführe.

Zum Vergleich unterscheidet das C ++ - Unit-Test-Framework von Google zwischen nicht schwerwiegenden EXPECT_*und schwerwiegenden ASSERT_*Behauptungen:

Die Behauptungen kommen paarweise, die dasselbe testen, aber unterschiedliche Auswirkungen auf die aktuelle Funktion haben. ASSERT_ * -Versionen erzeugen schwerwiegende Fehler, wenn sie fehlschlagen, und brechen die aktuelle Funktion ab. EXPECT_ * -Versionen erzeugen nicht schwerwiegende Fehler, die die aktuelle Funktion nicht abbrechen. Normalerweise werden EXPECT_ * bevorzugt, da dadurch mehr als ein Fehler in einem Test gemeldet werden kann. Sie sollten jedoch ASSERT_ * verwenden, wenn es nicht sinnvoll ist, fortzufahren, wenn die betreffende Zusicherung fehlschlägt.

Gibt es eine Möglichkeit, EXPECT_*bei Python ein ähnliches Verhalten zu erreichen unittest? Wenn nicht unittest, gibt es dann ein anderes Python-Unit-Test-Framework, das dieses Verhalten unterstützt?


Im Übrigen war ich neugierig, wie viele reale Tests von nicht schwerwiegenden Behauptungen profitieren könnten, und habe mir daher einige Codebeispiele angesehen (bearbeitet am 19.08.2014, um Suchcode anstelle von Google Code Search, RIP, zu verwenden). Von 10 zufällig ausgewählten Ergebnissen auf der ersten Seite enthielten alle Tests, die mehrere unabhängige Aussagen in derselben Testmethode machten. Alle würden von nicht tödlichen Behauptungen profitieren.

Bruce Christensen
quelle
2
Was hast du am Ende gemacht? Ich interessiere mich für dieses Thema (aus ganz anderen Gründen, die ich gerne an einem geräumigeren Ort als einem Kommentar diskutieren würde) und würde gerne Ihre Erfahrungen erfahren. Übrigens endet der Link "Codebeispiele" mit "Leider wurde dieser Dienst heruntergefahren". Wenn Sie also eine zwischengespeicherte Version davon haben, wäre ich interessiert, sie auch zu sehen.
Davide
Zum späteren Nachschlagen glaube ich, dass dies die äquivalente Suche auf dem aktuellen System ist, aber die Ergebnisse sind nicht mehr wie oben beschrieben.
ZAD-Man
2
@ David, ich habe am Ende nichts getan. Der Ansatz "nur eine Behauptung pro Methode machen" erscheint mir zu streng dogmatisch, aber die einzige praktikable (und wartbare) Lösung scheint Anthonys "catch and append" -Vorschlag zu sein. Das ist mir allerdings zu hässlich, deshalb habe ich mich nur an mehrere Asserts pro Methode gehalten, und ich muss öfter als nötig mit laufenden Tests leben, um alle Fehler zu finden.
Bruce Christensen
Das Python- Testframework namens PyTest ist sehr intuitiv und zeigt standardmäßig alle Assert- Fehler an. Das könnte eine Lösung für das Problem sein, mit dem Sie konfrontiert sind.
Surya Shekhar Chakraborty

Antworten:

9

Was Sie wahrscheinlich tun möchten, ist abzuleiten, unittest.TestCaseda dies die Klasse ist, die ausgelöst wird, wenn eine Behauptung fehlschlägt. Sie müssen Ihre neu entwerfen, TestCaseum nicht zu werfen (möglicherweise führen Sie stattdessen eine Liste der Fehler). Eine Neuarchitektur kann andere Probleme verursachen, die Sie lösen müssten. Beispielsweise müssen Sie möglicherweise ableiten TestSuite, um Änderungen zur Unterstützung der an Ihnen vorgenommenen Änderungen vorzunehmen TestCase.

Dietbuddha
quelle
1
Ich dachte mir, dass dies wahrscheinlich die endgültige Antwort sein würde, aber ich wollte meine Grundlagen abdecken und sehen, ob mir etwas fehlte. Vielen Dank!
Bruce Christensen
4
Ich würde sagen, es ist ein Overkill, dies zu überschreiben, TestCaseum weiche Behauptungen zu implementieren - sie sind in Python besonders einfach zu erstellen: Fangen Sie einfach alle Ihre AssertionErrors (möglicherweise in einer einfachen Schleife) ab und speichern Sie sie in einer Liste oder einem Satz , dann scheitern sie alle auf einmal. Einzelheiten finden Sie in der Antwort von @Anthony Batchelor.
dcsordas
2
@dscordas Hängt davon ab, ob dies für einen einmaligen Test ist oder ob Sie diese Fähigkeit für die meisten Tests haben möchten.
Dietbuddha
43

Eine andere Möglichkeit, nicht schwerwiegende Zusicherungen zu erhalten, besteht darin, die Zusicherungsausnahme zu erfassen und die Ausnahmen in einer Liste zu speichern. Stellen Sie dann sicher, dass diese Liste als Teil des TearDowns leer ist.

import unittest

class Car(object):
  def __init__(self, make, model):
    self.make = make
    self.model = make  # Copy and paste error: should be model.
    self.has_seats = True
    self.wheel_count = 3  # Typo: should be 4.

class CarTest(unittest.TestCase):
  def setUp(self):
    self.verificationErrors = []

  def tearDown(self):
    self.assertEqual([], self.verificationErrors)

  def test_init(self):
    make = "Ford"
    model = "Model T"
    car = Car(make=make, model=model)
    try: self.assertEqual(car.make, make)
    except AssertionError, e: self.verificationErrors.append(str(e))
    try: self.assertEqual(car.model, model)  # Failure!
    except AssertionError, e: self.verificationErrors.append(str(e))
    try: self.assertTrue(car.has_seats)
    except AssertionError, e: self.verificationErrors.append(str(e))
    try: self.assertEqual(car.wheel_count, 4)  # Failure!
    except AssertionError, e: self.verificationErrors.append(str(e))

if __name__ == "__main__":
    unittest.main()
Anthony Batchelor
quelle
2
Ich bin mir ziemlich sicher, dass ich Ihnen zustimme. So geht Selenium mit Überprüfungsfehlern im Python-Backend um.
Anthony Batchelor
Ja, das Problem bei dieser Lösung ist, dass alle Asserts als Fehler (nicht als Fehler) gezählt werden und die Art und Weise, wie die Fehler gerendert werden, nicht wirklich verwendbar ist. Auf jeden
Fall
Ich verwende diese Lösung in Kombination mit der Antwort von Dietbudda, indem ich alle Behauptungen unittest.TestCasemit Try / Except- Blöcken überschreibe .
Thodic
Für komplexe Testmuster ist dies die beste Lösung, um den kleinsten Fehler zu beseitigen, aber der Test sieht bei allen Versuchen / Ausnahmen ziemlich hässlich aus. Es ist ein Kompromiss zwischen vielen Tests und einem komplexen Einzeltest. Ich habe stattdessen ein Fehlerdiktat zurückgegeben. So kann ich das gesamte Testmuster in einem Test testen und die Lesbarkeit für meine gelegentlichen Python-Entwickler beibehalten.
MortenB
Das ist extrem klug, also Hut ab vor dir.
Courtsimas
30

Eine Option besteht darin, alle Werte gleichzeitig als Tupel zu aktivieren.

Zum Beispiel:

class CarTest(unittest.TestCase):
  def test_init(self):
    make = "Ford"
    model = "Model T"
    car = Car(make=make, model=model)
    self.assertEqual(
            (car.make, car.model, car.has_seats, car.wheel_count),
            (make, model, True, 4))

Die Ausgabe dieser Tests wäre:

======================================================================
FAIL: test_init (test.CarTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\temp\py_mult_assert\test.py", line 17, in test_init
    (make, model, True, 4))
AssertionError: Tuples differ: ('Ford', 'Ford', True, 3) != ('Ford', 'Model T', True, 4)

First differing element 1:
Ford
Model T

- ('Ford', 'Ford', True, 3)
?           ^ -          ^

+ ('Ford', 'Model T', True, 4)
?           ^  ++++         ^

Dies zeigt, dass sowohl das Modell als auch die Radanzahl falsch sind.

hwiechers
quelle
Das ist klug. Die beste Lösung, die ich bisher gefunden habe.
Chen Ni
7

Es wird als Anti-Muster angesehen, mehrere Asserts in einem einzigen Unit-Test zu haben. Es wird erwartet, dass ein einzelner Einheitentest nur eines testet. Vielleicht testen Sie zu viel. Teilen Sie diesen Test in mehrere Tests auf. Auf diese Weise können Sie jeden Test richtig benennen.

Manchmal ist es jedoch in Ordnung, mehrere Dinge gleichzeitig zu überprüfen. Zum Beispiel, wenn Sie Eigenschaften desselben Objekts bestätigen. In diesem Fall behaupten Sie tatsächlich, ob dieses Objekt korrekt ist. Eine Möglichkeit, dies zu tun, besteht darin, eine benutzerdefinierte Hilfsmethode zu schreiben, die weiß, wie auf diesem Objekt behauptet wird. Sie können diese Methode so schreiben, dass sie alle fehlerhaften Eigenschaften anzeigt oder beispielsweise den vollständigen Status des erwarteten Objekts und den vollständigen Status des tatsächlichen Objekts anzeigt, wenn eine Zusicherung fehlschlägt.

Steven
quelle
1
@ Bruce: Eine Bestätigung sollte fehlschlagen oder erfolgreich sein. Niemals etwas dazwischen. Der Test sollte vertrauenswürdig, lesbar und wartbar sein. Eine fehlerhafte Behauptung, die den Test nicht nicht besteht, ist eine schlechte Idee. Dies macht Ihre Tests zu kompliziert (was die Lesbarkeit und Wartbarkeit beeinträchtigt), und Tests, die fehlschlagen dürfen, machen es einfach, sie zu ignorieren, was bedeutet, dass sie nicht vertrauenswürdig sind.
Steven
8
Jeder Grund, warum der Rest des Tests nicht ausgeführt werden kann und dennoch tödlich ist. Ich würde denken, Sie könnten die Rückgabe des Fehlers irgendwo verzögern, um alle möglichen Fehler, die auftreten können, zusammenzufassen.
Dietbuddha
5
Ich denke, wir sagen beide dasselbe. Ich möchte, dass jede fehlerhafte Behauptung dazu führt, dass der Test fehlschlägt. Ich möchte nur, dass der Fehler auftritt, wenn die Testmethode zurückgegeben wird, und nicht sofort, wenn die Bestätigung getestet wird, wie @dietbuddha erwähnt hat. Dies würde es ermöglichen, alle Asserts in der Methode zu testen, so dass ich alle Fehler auf einmal sehen (und beheben) kann. Der Test ist immer noch vertrauenswürdig, lesbar und wartbar (noch mehr).
Bruce Christensen
10
Er sagt nicht, dass der Test nicht fehlschlagen sollte, wenn Sie die Behauptung treffen, er sagt, dass der Fehler die anderen Überprüfungen nicht verhindern sollte. Zum Beispiel teste ich gerade, dass bestimmte Verzeichnisse Benutzer, Gruppen und andere beschreibbare Verzeichnisse sind. Jedes ist eine separate Behauptung. Es wäre nützlich, aus der Testausgabe zu wissen, dass alle drei Fälle fehlschlagen, damit ich sie mit einem chmod-Aufruf beheben kann, anstatt "Pfad ist nicht vom Benutzer beschreibbar" zu erhalten und den Test erneut ausführen zu müssen, um "Pfad ist" zu erhalten nicht gruppenbeschreibbar "und so weiter. Obwohl ich denke, ich habe gerade argumentiert, dass es separate Tests sein sollten ...
Tim Keating
8
Nur weil die Bibliothek als unittest bezeichnet wird, bedeutet dies nicht, dass der Test ein isolierter Komponententest ist. Das unittest-Modul sowie Pytest und Nose und andere eignen sich hervorragend für Systemtests, Integrationstests usw. Die einzige Einschränkung besteht darin, dass Sie nur einmal ausfallen können. Es ist wirklich nervig. Ich würde wirklich gerne sehen, dass alle Assert-Funktionen entweder einen Parameter hinzufügen, mit dem Sie mit einem Fehler fortfahren können, oder eine Duplizierung der Assert-Funktionen namens "ExpectBlah", die so etwas tun. Dann wäre es viel einfacher, größere Funktionstests mit unittest zu schreiben.
Okken
6

Seit Python 3.4 können Sie auch Untertests verwenden :

def test_init(self):
    make = "Ford"
    model = "Model T"
    car = Car(make=make, model=model)
    with self.subTest(msg='Car.make check'):
        self.assertEqual(car.make, make)
    with self.subTest(msg='Car.model check'):
        self.assertEqual(car.model, model)
    with self.subTest(msg='Car.has_seats check'):
        self.assertTrue(car.has_seats)
    with self.subTest(msg='Car.wheel_count check'):
        self.assertEqual(car.wheel_count, 4)

(msg Parameter können Sie leichter feststellen, welcher Test fehlgeschlagen ist.)

Ausgabe:

======================================================================
FAIL: test_init (__main__.CarTest) [Car.model check]
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test.py", line 23, in test_init
    self.assertEqual(car.model, model)
AssertionError: 'Ford' != 'Model T'
- Ford
+ Model T


======================================================================
FAIL: test_init (__main__.CarTest) [Car.wheel_count check]
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test.py", line 27, in test_init
    self.assertEqual(car.wheel_count, 4)
AssertionError: 3 != 4

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=2)
Zuku
quelle
Dies sollte nun die akzeptierte Antwort sein, da sie am einfachsten in vorhandenen Code eingefügt werden kann.
Michael Scott Cuthbert
5

Führen Sie jede Behauptung in einer separaten Methode aus.

class MathTest(unittest.TestCase):
  def test_addition1(self):
    self.assertEqual(1 + 0, 1)

  def test_addition2(self):
    self.assertEqual(1 + 1, 3)

  def test_addition3(self):
    self.assertEqual(1 + (-1), 0)

  def test_addition4(self):
    self.assertEqaul(-1 + (-1), -1)
Lennart Regebro
quelle
5
Mir ist klar, dass dies eine mögliche Lösung ist, aber nicht immer praktisch. Ich suche nach etwas, das funktioniert, ohne einen ehemals zusammenhängenden Test in mehrere kleine Methoden aufzuteilen.
Bruce Christensen
@ Bruce Christensen: Wenn sie so zusammenhängend sind, bilden sie vielleicht eine Geschichte? Und dann können sie in Doctests gemacht werden, was in der Tat wird auch nach dem Scheitern fortsetzen.
Lennart Regebro
1
Ich habe eine Reihe von Tests, ungefähr so: 1. Daten laden, 2. Daten korrekt laden, 3. Daten ändern, 4. Änderungen bestätigen, die ordnungsgemäß funktioniert haben, 5. geänderte Daten speichern, 6. Daten bestätigen, die korrekt gespeichert wurden. Wie kann ich das mit dieser Methode machen? Es ist nicht sinnvoll, die Daten zu laden setup(), da dies einer der Tests ist. Aber wenn ich jede Behauptung in ihre eigene Funktion setze, muss ich dreimal Daten laden, und das ist eine enorme Verschwendung von Ressourcen. Was ist der beste Weg, um mit einer solchen Situation umzugehen?
naught101
Nun, Tests, die eine bestimmte Sequenz testen, sollten in derselben Testmethode durchgeführt werden.
Lennart Regebro
4

In PyPI gibt es ein Soft Assertion-Paket namens softest, das Ihre Anforderungen erfüllt. Es sammelt die Fehler, kombiniert Ausnahme- und Stack-Trace-Daten und meldet alles als Teil des Üblichenunittest Ausgabe.

Zum Beispiel dieser Code:

import softest

class ExampleTest(softest.TestCase):
    def test_example(self):
        # be sure to pass the assert method object, not a call to it
        self.soft_assert(self.assertEqual, 'Worf', 'wharf', 'Klingon is not ship receptacle')
        # self.soft_assert(self.assertEqual('Worf', 'wharf', 'Klingon is not ship receptacle')) # will not work as desired
        self.soft_assert(self.assertTrue, True)
        self.soft_assert(self.assertTrue, False)

        self.assert_all()

if __name__ == '__main__':
    softest.main()

... erzeugt diese Konsolenausgabe:

======================================================================
FAIL: "test_example" (ExampleTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\...\softest_test.py", line 14, in test_example
    self.assert_all()
  File "C:\...\softest\case.py", line 138, in assert_all
    self.fail(''.join(failure_output))
AssertionError: ++++ soft assert failure details follow below ++++

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
The following 2 failures were found in "test_example" (ExampleTest):
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Failure 1 ("test_example" method)
+--------------------------------------------------------------------+
Traceback (most recent call last):
  File "C:\...\softest_test.py", line 10, in test_example
    self.soft_assert(self.assertEqual, 'Worf', 'wharf', 'Klingon is not ship receptacle')
  File "C:\...\softest\case.py", line 84, in soft_assert
    assert_method(*arguments, **keywords)
  File "C:\...\Python\Python36-32\lib\unittest\case.py", line 829, in assertEqual
    assertion_func(first, second, msg=msg)
  File "C:\...\Python\Python36-32\lib\unittest\case.py", line 1203, in assertMultiLineEqual
    self.fail(self._formatMessage(msg, standardMsg))
  File "C:\...\Python\Python36-32\lib\unittest\case.py", line 670, in fail
    raise self.failureException(msg)
AssertionError: 'Worf' != 'wharf'
- Worf
+ wharf
 : Klingon is not ship receptacle

+--------------------------------------------------------------------+
Failure 2 ("test_example" method)
+--------------------------------------------------------------------+
Traceback (most recent call last):
  File "C:\...\softest_test.py", line 12, in test_example
    self.soft_assert(self.assertTrue, False)
  File "C:\...\softest\case.py", line 84, in soft_assert
    assert_method(*arguments, **keywords)
  File "C:\...\Python\Python36-32\lib\unittest\case.py", line 682, in assertTrue
    raise self.failureException(msg)
AssertionError: False is not true


----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=1)

HINWEIS : Ich habe erstellt und gepflegt softest.

skia.heliou
quelle
2

Ich mochte den Ansatz von @ Anthony-Batchelor, die AssertionError-Ausnahme zu erfassen. Aber eine leichte Abweichung von diesem Ansatz unter Verwendung von Dekorateuren und auch eine Möglichkeit, die Testfälle mit Bestanden / Nicht Bestanden zu melden.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import unittest

class UTReporter(object):
    '''
    The UT Report class keeps track of tests cases
    that have been executed.
    '''
    def __init__(self):
        self.testcases = []
        print "init called"

    def add_testcase(self, testcase):
        self.testcases.append(testcase)

    def display_report(self):
        for tc in self.testcases:
            msg = "=============================" + "\n" + \
                "Name: " + tc['name'] + "\n" + \
                "Description: " + str(tc['description']) + "\n" + \
                "Status: " + tc['status'] + "\n"
            print msg

reporter = UTReporter()

def assert_capture(*args, **kwargs):
    '''
    The Decorator defines the override behavior.
    unit test functions decorated with this decorator, will ignore
    the Unittest AssertionError. Instead they will log the test case
    to the UTReporter.
    '''
    def assert_decorator(func):
        def inner(*args, **kwargs):
            tc = {}
            tc['name'] = func.__name__
            tc['description'] = func.__doc__
            try:
                func(*args, **kwargs)
                tc['status'] = 'pass'
            except AssertionError:
                tc['status'] = 'fail'
            reporter.add_testcase(tc)
        return inner
    return assert_decorator



class DecorateUt(unittest.TestCase):

    @assert_capture()
    def test_basic(self):
        x = 5
        self.assertEqual(x, 4)

    @assert_capture()
    def test_basic_2(self):
        x = 4
        self.assertEqual(x, 4)

def main():
    #unittest.main()
    suite = unittest.TestLoader().loadTestsFromTestCase(DecorateUt)
    unittest.TextTestRunner(verbosity=2).run(suite)

    reporter.display_report()


if __name__ == '__main__':
    main()

Ausgabe von der Konsole:

(awsenv)$ ./decorators.py 
init called
test_basic (__main__.DecorateUt) ... ok
test_basic_2 (__main__.DecorateUt) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK
=============================
Name: test_basic
Description: None
Status: fail

=============================
Name: test_basic_2
Description: None
Status: pass
Zoro_77
quelle
2

erwarten ist sehr nützlich in gtest. Dies ist Python-Art in Kern und Code:

import sys
import unittest


class TestCase(unittest.TestCase):
    def run(self, result=None):
        if result is None:
            self.result = self.defaultTestResult()
        else:
            self.result = result

        return unittest.TestCase.run(self, result)

    def expect(self, val, msg=None):
        '''
        Like TestCase.assert_, but doesn't halt the test.
        '''
        try:
            self.assert_(val, msg)
        except:
            self.result.addFailure(self, sys.exc_info())

    def expectEqual(self, first, second, msg=None):
        try:
            self.failUnlessEqual(first, second, msg)
        except:
            self.result.addFailure(self, sys.exc_info())

    expect_equal = expectEqual

    assert_equal = unittest.TestCase.assertEqual
    assert_raises = unittest.TestCase.assertRaises


test_main = unittest.main
Ken
quelle
1

Ich hatte ein Problem mit der Antwort von @Anthony Batchelor, weil es mich gezwungen hätte, sie try...catchin meinen Unit-Tests zu verwenden. Stattdessen habe ich die try...catchLogik in eine Überschreibung der TestCase.assertEqualMethode gekapselt . Hier ist der Code:

import unittest
import traceback

class AssertionErrorData(object):

    def __init__(self, stacktrace, message):
        super(AssertionErrorData, self).__init__()
        self.stacktrace = stacktrace
        self.message = message

class MultipleAssertionFailures(unittest.TestCase):

    def __init__(self, *args, **kwargs):
        self.verificationErrors = []
        super(MultipleAssertionFailures, self).__init__( *args, **kwargs )

    def tearDown(self):
        super(MultipleAssertionFailures, self).tearDown()

        if self.verificationErrors:
            index = 0
            errors = []

            for error in self.verificationErrors:
                index += 1
                errors.append( "%s\nAssertionError %s: %s" % ( 
                        error.stacktrace, index, error.message ) )

            self.fail( '\n\n' + "\n".join( errors ) )
            self.verificationErrors.clear()

    def assertEqual(self, goal, results, msg=None):

        try:
            super( MultipleAssertionFailures, self ).assertEqual( goal, results, msg )

        except unittest.TestCase.failureException as error:
            goodtraces = self._goodStackTraces()
            self.verificationErrors.append( 
                    AssertionErrorData( "\n".join( goodtraces[:-2] ), error ) )

    def _goodStackTraces(self):
        """
            Get only the relevant part of stacktrace.
        """
        stop = False
        found = False
        goodtraces = []

        # stacktrace = traceback.format_exc()
        # stacktrace = traceback.format_stack()
        stacktrace = traceback.extract_stack()

        # /programming/54499367/how-to-correctly-override-testcase
        for stack in stacktrace:
            filename = stack.filename

            if found and not stop and \
                    not filename.find( 'lib' ) < filename.find( 'unittest' ):
                stop = True

            if not found and filename.find( 'lib' ) < filename.find( 'unittest' ):
                found = True

            if stop and found:
                stackline = '  File "%s", line %s, in %s\n    %s' % ( 
                        stack.filename, stack.lineno, stack.name, stack.line )
                goodtraces.append( stackline )

        return goodtraces

# class DummyTestCase(unittest.TestCase):
class DummyTestCase(MultipleAssertionFailures):

    def setUp(self):
        self.maxDiff = None
        super(DummyTestCase, self).setUp()

    def tearDown(self):
        super(DummyTestCase, self).tearDown()

    def test_function_name(self):
        self.assertEqual( "var", "bar" )
        self.assertEqual( "1937", "511" )

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

Ergebnisausgabe:

F
======================================================================
FAIL: test_function_name (__main__.DummyTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:\User\Downloads\test.py", line 77, in tearDown
    super(DummyTestCase, self).tearDown()
  File "D:\User\Downloads\test.py", line 29, in tearDown
    self.fail( '\n\n' + "\n\n".join( errors ) )
AssertionError: 

  File "D:\User\Downloads\test.py", line 80, in test_function_name
    self.assertEqual( "var", "bar" )
AssertionError 1: 'var' != 'bar'
- var
? ^
+ bar
? ^
 : 

  File "D:\User\Downloads\test.py", line 81, in test_function_name
    self.assertEqual( "1937", "511" )
AssertionError 2: '1937' != '511'
- 1937
+ 511
 : 

Weitere alternative Lösungen für die korrekte Stapelverfolgungserfassung finden Sie unter Wie kann TestCase.assertEqual () korrekt überschrieben werden, um die richtige Stapelverfolgung zu erstellen?

Nutzer
quelle
0

Ich glaube nicht, dass es eine Möglichkeit gibt, dies mit PyUnit zu tun, und möchte nicht, dass PyUnit auf diese Weise erweitert wird.

Ich halte mich lieber an eine Behauptung pro Testfunktion ( oder genauer gesagt an ein Konzept pro Test ) und würde sie test_addition()als vier separate Testfunktionen umschreiben . Dies würde nützlichere Informationen zum Ausfall geben, nämlich :

.FF.
======================================================================
FAIL: test_addition_with_two_negatives (__main__.MathTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_addition.py", line 10, in test_addition_with_two_negatives
    self.assertEqual(-1 + (-1), -1)
AssertionError: -2 != -1

======================================================================
FAIL: test_addition_with_two_positives (__main__.MathTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_addition.py", line 6, in test_addition_with_two_positives
    self.assertEqual(1 + 1, 3)  # Failure!
AssertionError: 2 != 3

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

FAILED (failures=2)

Wenn Sie entscheiden, dass dieser Ansatz nicht für Sie geeignet ist, finden Sie diese Antwort möglicherweise hilfreich.

Aktualisieren

Es sieht so aus, als würden Sie zwei Konzepte mit Ihrer aktualisierten Frage testen, und ich würde diese in zwei Komponententests aufteilen. Das erste ist, dass die Parameter beim Erstellen eines neuen Objekts gespeichert werden. Dies hätte zwei Behauptungen, eine für makeund eine fürmodel . Wenn der erste fehlschlägt, ist das, was eindeutig behoben werden muss, ob der zweite erfolgreich ist oder nicht, an dieser Stelle irrelevant.

Das zweite Konzept ist fragwürdiger ... Sie testen, ob einige Standardwerte initialisiert sind. Warum ? Es wäre sinnvoller, diese Werte an dem Punkt zu testen, an dem sie tatsächlich verwendet werden (und wenn sie nicht verwendet werden, warum sind sie dann dort?).

Beide Tests schlagen fehl und sollten beide. Wenn ich Unit-Tests mache, bin ich viel mehr am Scheitern interessiert als am Erfolg, da ich mich dort konzentrieren muss.

FF
======================================================================
FAIL: test_creation_defaults (__main__.CarTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_car.py", line 25, in test_creation_defaults
    self.assertEqual(self.car.wheel_count, 4)  # Failure!
AssertionError: 3 != 4

======================================================================
FAIL: test_creation_parameters (__main__.CarTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_car.py", line 20, in test_creation_parameters
    self.assertEqual(self.car.model, self.model)  # Failure!
AssertionError: 'Ford' != 'Model T'

----------------------------------------------------------------------
Ran 2 tests in 0.000s

FAILED (failures=2)
Johnsyweb
quelle
Würden Sie Car.test_init in vier Funktionen aufteilen?
Bruce Christensen
@ Bruce Christensen: Ich würde es wahrscheinlich in zwei Teile teilen. Aber selbst dann bin ich mir nicht sicher, ob Ihre Behauptungen nützlich sind. Siehe Update zur Antwort.
Johnsyweb
0

Mir ist klar, dass diese Frage vor Jahren buchstäblich gestellt wurde, aber jetzt gibt es (mindestens) zwei Python-Pakete, mit denen Sie dies tun können.

Eines ist am weichsten: https://pypi.org/project/softest/

Das andere ist Python-Delayed-Assert: https://github.com/pr4bh4sh/python-delayed-assert

Ich habe es auch nicht benutzt, aber sie sehen mir ziemlich ähnlich.

Todd Bradley
quelle