2012-03-22 9 views
10

Jest to problem, który pojawił się podczas wykonywania pojedynczego testu z wieloma niezależnymi trybami awaryjnymi ze względu na posiadanie wielu strumieni wyjściowych. Chciałem również pokazać wyniki potwierdzania danych we wszystkich tych trybach, niezależnie od tego, które wcześniej zawiodły. Python's unittest nie ma takiej funkcji poza używaniem Suite do reprezentowania pojedynczego testu, co było niedopuszczalne, ponieważ mój pojedynczy test zawsze wymagał uruchomienia jako pojedyncza jednostka; to po prostu nie uchwyca natury rzeczy.W jaki sposób obsłużyć wiele asserts w jednym unittest Python?

Praktycznym przykładem jest testowanie obiektu, który generuje również dziennik. Chcesz potwierdzić wyjście z jego metod, ale chcesz także potwierdzić wynik logu. Te dwa wyjścia wymagają różnych testów, które mogą być starannie wyrażone jako dwa z wyrażenia asserts, ale także nie chcesz, aby jedna z nich ukryła możliwą awarię drugiej w teście. Więc naprawdę musisz przetestować oba jednocześnie.

I cobbled razem ten przydatny mały widget, aby rozwiązać mój problem.

def logFailures(fnList): 
    failurelog = [] 
    for fn in fnList: 
     try: 
      fn() 
     except AssertionError as e: 
      failurelog.append("\nFailure %d: %s" % (len(failurelog)+1,str(e))) 

    if len(failurelog) != 0: 
     raise AssertionError(
      "%d failures within test.\n %s" % (len(failurelog),"\n".join(failurelog)) 
     ) 

Który jest używany tak:

def test__myTest(): 
    # do some work here 
    logFailures([ 
     lambda: assert_(False,"This test failed."), 
     lambda: assert_(False,"This test also failed."), 
    ]) 

Powoduje to, że logFailures() rzuci wyjątek, który zawiera rejestr wszystkich twierdzeń, które zostały podniesione w metodach obrębie listy.

Pytanie: Podczas wykonywania tego zadania zastanawiam się, czy istnieje lepszy sposób na poradzenie sobie z tym, poza tym, że trzeba przejść do długości tworzenia zagnieżdżonych zestawów testów i tak dalej?

+3

"Ty też nie chcesz, aby jedna osoba ukryła możliwą awarię drugiej w teście". Jeśli chcesz przetestować dwie różne rzeczy, wykonaj dwa różne testy! –

+1

"Ty też nie chcesz, aby jedna z nich ukryła możliwą porażkę drugiej w teście". Tak - chcę: to są testy jednostkowe. Jeśli jeden z testów się nie powiedzie, popraw błąd i ponownie uruchom testy. –

Odpowiedz

12

Nie zgadzam się z dominującą opinią, że należy napisać metodę testową dla każdego stwierdzenia. Są sytuacje, w których chcesz sprawdzić wiele rzeczy w jednej metodzie testu. Oto moja odpowiedź na jak to zrobić:

# Works with unittest in Python 2.7 
class ExpectingTestCase(unittest.TestCase): 
    def run(self, result=None): 
     self._result = result 
     self._num_expectations = 0 
     super(ExpectingTestCase, self).run(result) 

    def _fail(self, failure): 
     try: 
      raise failure 
     except failure.__class__: 
      self._result.addFailure(self, sys.exc_info()) 

    def expect_true(self, a, msg): 
     if not a: 
      self._fail(self.failureException(msg)) 
     self._num_expectations += 1 

    def expect_equal(self, a, b, msg=''): 
     if a != b: 
      msg = '({}) Expected {} to equal {}. '.format(self._num_expectations, a, b) + msg 
      self._fail(self.failureException(msg)) 
     self._num_expectations += 1 

A oto kilka sytuacji, w których myślę, że to użyteczne i nie ryzykowne:

1) Jeżeli chcesz przetestować kod dla różnych zestawów danych. Tutaj mamy funkcję add() i chcę przetestować ją za pomocą kilku przykładowych danych wejściowych. Zapisanie 3 metod testowych dla 3 zestawów danych oznacza powtarzanie się, co jest złe. Zwłaszcza, jeśli telefon był bardziej skomplikowany .:

class MyTest(ExpectingTestCase): 
    def test_multiple_inputs(self): 
     for a, b, expect in ([1,1,2], [0,0,0], [2,2,4]): 
      self.expect_equal(expect, add(a,b), 'inputs: {} {}'.format(a,b)) 

2) Gdy chcesz sprawdzić wiele wyjść z funkcji. Chcę sprawdzić każde wyjście, ale nie chcę pierwszego błędu, aby zamaskować pozostałe dwa.

class MyTest(ExpectingTestCase): 
    def test_things_with_no_side_effects(self): 
     a, b, c = myfunc() 
     self.expect_equal('first value', a) 
     self.expect_equal('second value', b) 
     self.expect_equal('third value', c) 

3) Testowanie rzeczy z dużymi kosztami konfiguracji. Testy muszą przebiegać szybko lub ludzie przestają z nich korzystać. Niektóre testy wymagają połączenia z bazą danych lub siecią, co zajmuje sekundę, która naprawdę spowolniłaby twój test. Jeśli testujesz samo połączenie db, to prawdopodobnie musisz przyjąć prędkość działania. Ale jeśli testujesz coś niezwiązanego, chcemy wykonać powolną konfigurację raz na cały zestaw kontroli.

+1

Coś takiego powinno być domyślnie obecne w frameworkach testów jednostkowych. Czy ktoś zna taki, który ma tę funkcjonalność? – pmos

10

To wydaje mi się zbytnim inżynierią. Albo:

  • Użyj dwóch znaków w jednym przypadku testowym. Jeśli pierwszy dowód nie powiedzie się, to prawda, nie będziesz wiedział, czy drugie twierdzenie minęło, czy nie. Ale i tak naprawisz kod, więc napraw to, a potem dowiesz się, czy minęło drugie zdanie.

  • Napisz dwa testy, jeden do sprawdzenia każdego warunku. Jeśli obawiasz się zduplikowanego kodu w testach, umieść większość kodu w metodzie pomocniczej, którą wywołujesz z testów.

3

Przy użyciu podtestu, wykonanie nie zatrzyma po pierwszym niepowodzeniu https://docs.python.org/3/library/unittest.html#subtests

Oto przykład z dwóch nie twierdzi:

class TestMultipleAsserts(unittest.TestCase): 

    def test_multipleasserts(self): 
     with self.subTest(): 
      self.assertEqual(1, 0) 
     with self.subTest(): 
      self.assertEqual(2, 0) 

wyjściowe będą:

====================================================================== 
FAIL: test_multipleasserts (__main__.TestMultipleAsserts) (<subtest>) 
---------------------------------------------------------------------- 
Traceback (most recent call last): 
    File "./test.py", line 9, in test_multipleasserts 
    self.assertEqual(1, 0) 
AssertionError: 1 != 0 

====================================================================== 
FAIL: test_multipleasserts (__main__.TestMultipleAsserts) (<subtest>) 
---------------------------------------------------------------------- 
Traceback (most recent call last): 
    File "./test.py", line 11, in test_multipleasserts 
    self.assertEqual(2, 0) 
AssertionError: 2 != 0 

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

FAILED (failures=2) 

Możesz łatwo łączyć podtest, wykonując następujące czynności:

class MyTestCase(unittest.TestCase): 
    def expectEqual(self, first, second, msg=None): 
     with self.subTest(): 
      self.assertEqual(first, second, msg) 

class TestMA(MyTestCase): 
    def test_ma(self): 
     self.expectEqual(3, 0) 
     self.expectEqual(4, 0)