Comment gérer plusieurs asserts au sein d'un même unittest Python?

il s'agit d'un problème qui est apparu lors de l'exécution d'un seul test qui avait plusieurs modes de défaillance indépendants, en raison d'avoir plusieurs flux de sortie. J'ai également voulu montrer les résultats de l'affirmation des données sur tous ces modes, indépendamment de ce qui a échoué en premier. L'unittest de Python n'a pas cette fonctionnalité en dehors de l'utilisation d'une Suite pour représenter le test unique, ce qui était inacceptable puisque mon test unique devait toujours être exécuté en tant qu'unité unique; il ne saisit tout simplement pas la nature du chose.

un exemple pratique est de tester un objet qui génère également un log. Vous voulez affirmer la sortie de ses méthodes, mais vous voulez aussi affirmer la sortie log. Les deux sorties nécessitent des tests différents, qui peuvent être exprimés clairement comme deux du stock affirme expressions, mais vous ne voulez pas non plus l'échec de l'un pour cacher l'échec possible de l'autre dans le test. Donc vous devez vraiment tester les deux en même temps.

j'ai bricolé ce utile petit machin pour résoudre mon problème.

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

Qui est utilisé comme suit:

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

le résultat est que logFailures () va soulever une exception qui contient un log de toutes les assertions qui ont été soulevées dans les méthodes dans la liste.

la question: pendant que cela fait le travail, je me demande s'il y a une meilleure façon de gérer cela, autre que d'avoir à aller à la longueur de créer des suites imbriquées de tests et ainsi de suite?

10
demandé sur Eric Anderton 2012-03-22 23:40:28

3 réponses

J'ai l'impression que c'est trop technique pour moi. Soit:

  • utiliser deux assertions dans un cas d'essai. Si la première affirmation échoue, c'est vrai, vous ne saurez pas si la seconde affirmation a passé ou non. Mais vous allez corriger le code de toute façon, le réparer, et puis vous verrez si le deuxième affirmer passé.

  • Ecrire deux tests, un pour vérifier chaque condition. Si vous craignez le code dupliqué dans les tests, mettez le gros du code dans une méthode helper qui vous appelez à partir des essais.

12
répondu Ned Batchelder 2013-01-01 03:25:57

Je ne suis pas d'accord avec l'opinion dominante qu'on devrait écrire une méthode d'essai pour chaque affirmation. Il y a des situations où vous voulez vérifier plusieurs choses dans une méthode de test. Voici ma réponse pour savoir comment faire:

# 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

Et voici quelques situations où je pense que c'est utile et pas risqué:

1) Lorsque vous voulez tester le code pour différents ensembles de données. Ici, nous avons un ajout() et la fonction que je veux tester avec quelques exemples. Pour écrire 3 méthodes d'essai pour les 3 ensembles de données signifie se répéter ce qui est mauvais. Surtout si l'appel a été plus élaborées.:

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) Lorsque vous voulez vérifier plusieurs sorties d'une fonction. Je veux vérifier chaque sortie, mais je ne veux pas d'un premier échec pour masquer les deux autres.

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) Tester des choses avec de lourds frais d'installation. Les Tests doivent être rapides ou les gens cessent de les utiliser. Certains tests nécessitent une connexion db ou réseau qui prend une seconde ce qui ralentirait vraiment votre test. Si vous testez la connexion db Elle-même, alors vous avez probablement besoin de prendre la vitesse de frappe. Mais si vous testez quelque chose sans rapport, nous voulons faire la configuration lente une fois pour toute une série de contrôles.

12
répondu Winston 2013-09-19 22:08:56

avec l'utilisation d'un sous-test, l'exécution ne s'arrêterait pas après le premier échec https://docs.python.org/3/library/unittest.html#subtests

voici un exemple avec deux fail asserts:

class TestMultipleAsserts(unittest.TestCase):

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

Sortie sera:

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

vous pouvez facilement envelopper subtest comme suit

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)
5
répondu Vitalii Blagodir 2017-01-25 14:28:33