Kontynuacja w Python's unittest, gdy asercja nie powiedzie się


84

EDYCJA: przełączono na lepszy przykład i wyjaśniono, dlaczego jest to prawdziwy problem.

Chciałbym napisać testy jednostkowe w Pythonie, które będą kontynuowane, gdy asercja nie powiedzie się, tak żebym mógł zobaczyć wiele niepowodzeń w jednym teście. Na przykład:

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!

Tutaj celem testu jest upewnienie się, że Car's __init__poprawnie ustawia pola. Mógłbym podzielić to na cztery metody (i często jest to świetny pomysł), ale w tym przypadku bardziej czytelne jest zachowanie go jako pojedynczej metody, która testuje pojedynczą koncepcję („obiekt jest poprawnie zainicjowany”).

Jeśli założymy, że najlepiej tutaj nie przerywać metody, to mam nowy problem: nie widzę wszystkich błędów naraz. Kiedy naprawię modelbłąd i ponownie uruchomię test, wheel_countpojawia się błąd. Zaoszczędziłoby mi to czas, aby zobaczyć oba błędy podczas pierwszego uruchomienia testu.

Dla porównania, Google w C ++ framework testów jednostkowych rozróżnia między niezakończonych zgonem EXPECT_*twierdzeń i śmiertelnych ASSERT_*twierdzeń:

Twierdzenia występują w parach, które testują to samo, ale mają różny wpływ na bieżącą funkcję. Wersje ASSERT_ * generują krytyczne błędy w przypadku niepowodzenia i przerywają bieżącą funkcję. Wersje EXPECT_ * generują niepowodzenia niekrytyczne, które nie przerywają bieżącej funkcji. Zwykle preferowane są EXPECT_ *, ponieważ pozwalają na zgłoszenie więcej niż jednego błędu w teście. Należy jednak użyć ASSERT_ *, jeśli nie ma sensu kontynuować, gdy dane stwierdzenie zawodzi.

Czy istnieje sposób, aby uzyskać EXPECT_*podobne zachowanie w Pythonie unittest? Jeśli nie unittest, to czy istnieje inna platforma testów jednostkowych Pythona, która obsługuje to zachowanie?


Nawiasem mówiąc, byłem ciekawy, ile rzeczywistych testów może skorzystać na niekrytycznych asercjach, więc przyjrzałem się kilku przykładom kodu (edytowanym 2014-08-19, aby używać kodu wyszukiwania zamiast Google Code Search, RIP). Spośród 10 losowo wybranych wyników z pierwszej strony wszystkie zawierały testy, które zawierały wiele niezależnych stwierdzeń w tej samej metodzie testowej. Wszyscy skorzystaliby na twierdzeniach nie prowadzących do śmierci.


2
Co ostatecznie zrobiłeś? Interesuje mnie ten temat (z zupełnie innych powodów, które chętnie omówię w bardziej przestronnym miejscu niż komentarz) i chciałbym poznać Twoje doświadczenia. Nawiasem mówiąc, link „przykłady kodu” kończy się na „Niestety, ta usługa została zamknięta”, więc jeśli masz wersję w pamięci podręcznej, to też chciałbym ją zobaczyć.
Davide,

Dla porównania w przyszłości, uważam ten jest odpowiednikiem szukaj w obecnym systemie, ale wyniki nie są już w sposób opisany powyżej.
ZAD-Man,

2
@Davide, w końcu nic nie zrobiłem. Podejście „tylko jedno stwierdzenie na metodę” wydaje mi się zbyt sztywno dogmatyczne, ale jedynym wykonalnym (i możliwym do utrzymania) rozwiązaniem wydaje się być sugestia Anthony'ego „złap i dołącz”. Jest to jednak dla mnie zbyt brzydkie, więc po prostu utknąłem z wieloma potwierdzeniami na metodę i będę musiał żyć z uruchamianiem testów więcej razy niż potrzeba, aby znaleźć wszystkie niepowodzenia.
Bruce Christensen

Struktura testowa Pythona o nazwie PyTest jest dość intuicyjna i domyślnie pokazuje wszystkie błędy assertów . To może być obejście problemu, z którym się zmagasz.
Surya Shekhar Chakraborty

Odpowiedzi:


9

Prawdopodobnie będziesz chciał wyprowadzić, unittest.TestCaseponieważ jest to klasa, która generuje, gdy asercja nie powiedzie się. Będziesz musiał zmienić architekturę, TestCaseaby nie rzucać (może zamiast tego zachować listę awarii). Zmiana architektury może spowodować inne problemy, które musiałbyś rozwiązać. Na przykład może się okazać, że będziesz musiał wyprowadzić, TestSuiteaby wprowadzić zmiany wspierające zmiany wprowadzone w pliku TestCase.


1
Pomyślałem, że to prawdopodobnie będzie ostateczna odpowiedź, ale chciałem zakryć swoje bazy i sprawdzić, czy czegoś mi brakuje. Dzięki!
Bruce Christensen

4
Powiedziałbym, że przesadą jest przesłonięcie TestCaseze względu na implementację miękkich asercji - są one szczególnie łatwe do wykonania w Pythonie: po prostu złap wszystkie swoje AssertionError(może w prostej pętli) i zapisz je na liście lub w zestawie , a następnie zawieść je wszystkie na raz. Sprawdź odpowiedź @Anthony Batchelor, aby uzyskać szczegółowe informacje.
dcsordas

2
@dscordas Zależy od tego, czy jest to jednorazowy test, czy też chcesz mieć tę możliwość w przypadku większości testów.
dietbuddha

44

Innym sposobem na uzyskanie potwierdzeń niekrytycznych jest przechwycenie wyjątku potwierdzenia i zapisanie wyjątków na liście. Następnie potwierdź, że ta lista jest pusta jako część tearDown.

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

2
Jestem pewien, że się z tobą zgadzam. W ten sposób Selenium radzi sobie z błędami weryfikacji w backendzie Pythona.
Anthony Batchelor

Tak, problem z tym rozwiązaniem polega na tym, że wszystkie potwierdzenia są liczone jako błędy (a nie błędy), a sposób renderowania błędów nie jest tak naprawdę użyteczny. W każdym razie jest sposób, a funkcję renderowania można łatwo poprawić
eMarine

Używam tego rozwiązania w połączeniu z odpowiedzią Dietbuddy, nadpisując wszystkie potwierdzenia za unittest.TestCasepomocą bloków try / except.
tego

W przypadku złożonych wzorców testowych jest to najlepsze rozwiązanie w celu pokonania najmniejszego błędu, ale sprawia, że ​​test wygląda raczej brzydko przy wszystkich próbach / wyjątkach. jest to kompromis między wieloma testami a złożonym pojedynczym testem. Zamiast tego zacząłem zwracać komunikat o błędzie. Mogę więc przetestować cały wzorzec w jednym teście i zachować czytelność dla innych zwykłych programistów Pythona.
MortenB

Jest to niezwykle sprytne, więc czapki z głów.
courimas

31

Jedną z opcji jest potwierdzenie wszystkich wartości naraz jako krotki.

Na przykład:

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

Wynik tych testów byłby następujący:

======================================================================
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)
?           ^  ++++         ^

To pokazuje, że zarówno model, jak i liczba kół są nieprawidłowe.


To jest sprytne. Najlepsze rozwiązanie, jakie do tej pory znalazłem.
Chen Ni,

8

Od Pythona 3.4 możesz także używać podtestów :

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)

( msgparametr służy do łatwiejszego określenia, który test się nie powiódł).

Wynik:

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

1
To powinna być teraz akceptowana odpowiedź jako najłatwiejsza do umieszczenia w istniejącym kodzie.
Michael Scott Cuthbert

7

Za anty-wzorzec uważa się posiadanie wielu potwierdzeń w jednym teście jednostkowym. Oczekuje się, że pojedynczy test jednostkowy przetestuje tylko jedną rzecz. Być może za dużo testujesz. Rozważ podzielenie tego testu na wiele testów. W ten sposób możesz poprawnie nazwać każdy test.

Czasami jednak dobrze jest sprawdzić kilka rzeczy jednocześnie. Na przykład, gdy potwierdzasz właściwości tego samego obiektu. W takim przypadku faktycznie zapewniasz, czy ten obiekt jest poprawny. Sposobem na to jest napisanie niestandardowej metody pomocniczej, która wie, jak potwierdzić ten obiekt. Możesz napisać tę metodę w taki sposób, aby pokazywała wszystkie niepoprawne właściwości lub na przykład pokazywała kompletny stan oczekiwanego obiektu i kompletny stan rzeczywistego obiektu, gdy asert nie powiedzie się.


1
@Bruce: potwierdzenie powinno się nie powieść lub zakończyć. Nigdy czegoś pomiędzy. Test powinien być wiarygodny, czytelny i łatwy w utrzymaniu. Niepełne twierdzenie, które nie obleje testu, to zły pomysł. To sprawia, że ​​testy są zbyt skomplikowane (co obniża czytelność i łatwość utrzymania), a posiadanie testów, które mogą się nie powieść, ułatwia ich zignorowanie, co oznacza, że ​​nie są godne zaufania.
Steven,

8
jakikolwiek powód, dla którego reszta testu nie może zostać uruchomiona i nadal kończy się śmiercią. Wydaje mi się, że można gdzieś opóźnić powrót awarii na rzecz zsumowania wszystkich możliwych awarii, które mogą wystąpić.
dietbuddha

5
Myślę, że obaj mówimy to samo. Chcę, aby każde stwierdzenie o niepowodzeniu powodowało niepowodzenie testu; po prostu chcę, aby niepowodzenie wystąpiło po powrocie metody testowej, a nie natychmiast po przetestowaniu asercji, jak wspomniał @dietbuddha. Pozwoliłoby to przetestować wszystkie potwierdzenia w metodzie, tak żebym mógł zobaczyć (i naprawić) wszystkie błędy za jednym razem. Test jest nadal godny zaufania, czytelny i łatwy w utrzymaniu (a nawet bardziej).
Bruce Christensen,

10
Nie mówi, że test nie powinien zakończyć się niepowodzeniem, gdy trafisz w potwierdzenie, ale mówi, że niepowodzenie nie powinno przeszkodzić innym testom. Na przykład teraz testuję, czy określone katalogi są dostępne do zapisu dla użytkowników, grup i innych. Każdy jest oddzielnym stwierdzeniem. Przydałoby się wiedzieć na podstawie wyników testu, że wszystkie trzy przypadki kończą się niepowodzeniem, więc mogę je naprawić jednym wywołaniem chmod, zamiast otrzymywać informację „Ścieżka nie jest zapisywalna przez użytkownika” i trzeba ponownie uruchomić test, aby uzyskać informację „Ścieżka jest bez możliwości zapisu w grupie ”i tak dalej. Chociaż wydaje mi się, że właśnie argumentowałem, że powinny to być osobne testy ...
Tim Keating,

8
Tylko dlatego, że biblioteka nazywa się unittest, nie oznacza to, że test jest izolowanym testem jednostkowym. Moduł unittest, jak również pytest i nose i inne świetnie sprawdzają się przy testach systemowych, testach integracyjnych itp. Z jednym zastrzeżeniem, że możesz zawieść tylko raz. To naprawdę irytujące. Naprawdę chciałbym zobaczyć, jak wszystkie funkcje assert dodają parametr, który pozwala na kontynuowanie błędu, lub powielają funkcje assert o nazwie ExpectBlah, które robią coś takiego. Wtedy byłoby o wiele łatwiej pisać większe testy funkcjonalne za pomocą unittest.
Okken

5

Wykonaj każdą asercję w oddzielnej metodzie.

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)

5
Zdaję sobie sprawę, że to jedno z możliwych rozwiązań, ale nie zawsze jest to praktyczne. Szukam czegoś, co działa bez dzielenia jednego wcześniej spójnego testu na kilka małych metod.
Bruce Christensen,

@Bruce Christensen: Jeśli są tak spójni, to może tworzą historię? A potem można je przekształcić w testy kontrolne, które rzeczywiście będą kontynuowane nawet po niepowodzeniu.
Lennart Regebro

1
Mam zestaw testów, coś takiego: 1. załaduj dane, 2. potwierdź dane załadowane poprawnie, 3. zmodyfikuj dane, 4. potwierdź modyfikację działały poprawnie, 5. zapisz zmodyfikowane dane, 6. asertuj dane zapisane poprawnie. Jak mogę to zrobić tą metodą? ładowanie danych nie ma sensu setup(), ponieważ to jeden z testów. Ale jeśli umieszczę każde stwierdzenie w jego własnej funkcji, muszę załadować dane 3 razy, a to ogromna strata zasobów. Jaki jest najlepszy sposób radzenia sobie w takiej sytuacji?
naught101

Cóż, testy sprawdzające określoną sekwencję powinny być przeprowadzone w tej samej metodzie testowej.
Lennart Regebro

4

W PyPI jest pakiet asercji miękkiej o nazwie softest, który poradzi sobie z Twoimi wymaganiami. Działa poprzez zbieranie informacji o błędach, łączenie danych o wyjątkach i danych śledzenia stosu oraz raportowanie tego wszystkiego w ramach zwykłych unittestdanych wyjściowych.

Na przykład ten kod:

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

... generuje takie wyjście konsoli:

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

UWAGA : stworzyłem i utrzymuję softest.


3

oczekiwanie jest bardzo przydatne w gtest. Tak wygląda Python w skrócie i kod:

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

2

Podobało mi się podejście @ Anthony-Batchelor, aby uchwycić wyjątek AssertionError. Ale niewielka zmiana w tym podejściu przy użyciu dekoratorów, a także sposób zgłaszania przypadków testów z wynikiem pozytywnym / negatywnym.

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

Dane wyjściowe z konsoli:

(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

1

Miałem problem z odpowiedzią od @Anthony Batchelor, ponieważ zmusiłaby mnie to do użycia try...catchwewnątrz moich testów jednostkowych. Zamiast tego zawarłem try...catchlogikę w zastąpieniu TestCase.assertEqualmetody. Oto kod:

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

Wynik końcowy:

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
 : 

Więcej alternatywnych rozwiązań dla prawidłowego przechwytywania śledzenia stosu można opublikować na stronie Jak poprawnie zastąpić TestCase.assertEqual (), tworząc właściwy ślad stosu?


0

Myślę, że nie ma sposobu, aby to zrobić z PyUnit i nie chciałbym, aby PyUnit był rozszerzany w ten sposób.

Wolę trzymać się jednego potwierdzenia na funkcję testową ( lub dokładniej mówiąc, że jedna koncepcja na test ) i przepisałbym test_addition()jako cztery oddzielne funkcje testowe. Dałoby to bardziej przydatne informacje na temat awarii, a mianowicie :

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

Jeśli zdecydujesz, że to podejście nie jest dla Ciebie, możesz znaleźć tę odpowiedź pomocna.

Aktualizacja

Wygląda na to, że testujesz dwie koncepcje za pomocą zaktualizowanego pytania i podzieliłbym je na dwa testy jednostkowe. Po pierwsze, parametry są zapisywane podczas tworzenia nowego obiektu. To miałoby dwa twierdzenia, jedno za makei jedno zamodel . Jeśli pierwsza zawiedzie, to wyraźnie trzeba to naprawić, to czy druga się powiedzie, czy nie, nie ma znaczenia w tym momencie.

Druga koncepcja jest bardziej wątpliwa… Testujesz, czy niektóre wartości domyślne są inicjalizowane. Dlaczego ? Bardziej przydatne byłoby przetestowanie tych wartości w momencie, w którym są faktycznie używane (a jeśli nie są używane, to dlaczego tam są?).

Oba te testy kończą się niepowodzeniem i oba powinny. Kiedy testuję jednostki, bardziej interesuje mnie porażka niż sukces, ponieważ na tym muszę się skoncentrować.

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)

Czy więc podzieliłbyś Car.test_init na cztery funkcje?
Bruce Christensen,

@Bruce Christensen: Prawdopodobnie podzieliłbym to na dwie części. Ale nawet wtedy nie jestem pewien, czy twoje twierdzenia są przydatne. Zobacz aktualizację, aby odpowiedzieć.
Johnsyweb,

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.