python mécanisme recent - Poursuivre dans l'unittest de Python quand une assertion échoue




5 Answers

Ce que vous voudrez probablement faire est de dériver unittest.TestCase car c'est la classe qui se déclenche quand une assertion échoue. Vous devrez réorganiser votre TestCase pour ne pas lancer (peut-être garder une liste d'échecs à la place). Ré-architecturer des choses peut causer d'autres problèmes que vous auriez à résoudre. Par exemple, vous devrez peut-être dériver TestSuite pour apporter des modifications à l'appui des modifications apportées à votre TestCase .

rencontre cas

EDIT: passé à un meilleur exemple, et clarifié pourquoi c'est un vrai problème.

Je voudrais écrire des tests unitaires en Python qui continuent à s'exécuter quand une assertion échoue, de sorte que je puisse voir plusieurs échecs dans un seul test. Par exemple:

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!

Ici, le but du test est de s'assurer que __init__ de __init__ définit correctement ses champs. Je pourrais le décomposer en quatre méthodes (et c'est souvent une bonne idée), mais dans ce cas je pense qu'il est plus lisible de le garder comme une méthode unique qui teste un seul concept ("l'objet est initialisé correctement").

Si nous supposons qu'il est préférable ici de ne pas casser la méthode, alors j'ai un nouveau problème: je ne peux pas voir toutes les erreurs à la fois. Lorsque je wheel_count erreur du model et réexécute le test, l'erreur wheel_count apparaît. Cela me ferait gagner du temps pour voir les deux erreurs lorsque je passerai le test pour la première fois.

À titre de comparaison, le cadre de test unitaire C ++ de Google fait la distinction entre les assertions EXPECT_* non fatales et les assertions ASSERT_* fatales:

Les assertions viennent par paires qui testent la même chose mais ont des effets différents sur la fonction actuelle. Les versions ASSERT_ * génèrent des échecs fatals lorsqu'elles échouent et abandonnent la fonction en cours. Les versions EXPECT_ * génèrent des échecs non-fatals, qui n'annulent pas la fonction en cours. Habituellement, EXPECT_ * est préféré, car ils permettent de signaler plus d'un échec dans un test. Cependant, vous devriez utiliser ASSERT_ * si cela n'a pas de sens de continuer lorsque l'assertion en question échoue.

Existe-t-il un moyen d'obtenir EXPECT_* comportement semblable à EXPECT_* dans l' unittest de Python? Si ce n'est pas le cas, y a-t-il un autre framework de test d'unité Python qui supporte ce comportement?

Incidemment, j'étais curieux de savoir combien de tests réels pourraient bénéficier d'assertions non fatales, donc j'ai regardé quelques exemples de code (édité 2014-08-19 pour utiliser searchcode au lieu de Google Code Search, RIP). Sur les 10 résultats sélectionnés au hasard de la première page, tous contenaient des tests qui faisaient plusieurs assertions indépendantes dans la même méthode de test. Tous bénéficieraient d'assertions non fatales.




Une option est affirmer sur toutes les valeurs à la fois comme un tuple.

Par exemple:

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

La sortie de ces tests serait:

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

Cela montre que le modèle et le nombre de roues sont incorrects.




Est-ce que chacun affirme dans une méthode séparée.

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)



attendre est très utile dans gtest. C'est un moyen python dans gist , et 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



Je ne pense pas qu'il existe un moyen de faire cela avec PyUnit et je ne voudrais pas voir PyUnit étendu de cette façon.

Je préfère m'en tenir à une assertion par fonction de test ( ou plus précisément affirmer un concept par test ) et réécrire test_addition() comme quatre fonctions de test distinctes. Cela donnerait des informations plus utiles sur l'échec, à savoir :

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

Si vous décidez que cette approche n'est pas pour vous, vous pouvez trouver cette réponse utile.

Mettre à jour

Il semble que vous testiez deux concepts avec votre question mise à jour et je les diviserais en deux tests unitaires. Le premier étant que les paramètres sont stockés lors de la création d'un nouvel objet. Cela aurait deux assertions, une pour make et un pour le model . Si le premier échoue, il est clair que cela doit être corrigé, que le second soit réussi ou non, ce qui n'est pas pertinent à ce stade.

Le deuxième concept est plus discutable ... Vous testez si certaines valeurs par défaut sont initialisées. Pourquoi ? Il serait plus utile de tester ces valeurs au moment où elles sont réellement utilisées (et si elles ne sont pas utilisées, pourquoi sont-elles là?).

Ces deux tests échouent, et les deux devraient l'être. Quand je fais des tests unitaires, je m'intéresse beaucoup plus à l'échec qu'à la réussite car c'est là que je dois me concentrer.

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)



Related