web-dev-qa-db-fra.com

Affirmer qu’une méthode a été appelée dans un Python unité de test

Supposons que j'ai le code suivant dans un Python test d'unité:

aw = aps.Request("nv1")
aw2 = aps.Request("nv2", aw)

Existe-t-il un moyen simple d’affirmer qu’une méthode particulière (dans mon cas, aw.Clear()) a été appelée au cours de la deuxième ligne du test? par exemple. y a-t-il quelque chose comme ça:

#pseudocode:
assertMethodIsCalled(aw.Clear, lambda: aps.Request("nv2", aw))
68
Mark Heath

J'utilise Mock (qui est maintenant nittest.mock sur py3.3 +) pour cela:

from mock import patch
from PyQt4 import Qt


@patch.object(Qt.QMessageBox, 'aboutQt')
def testShowAboutQt(self, mock):
    self.win.actionAboutQt.trigger()
    self.assertTrue(mock.called)

Pour votre cas, cela pourrait ressembler à ceci:

import mock
from mock import patch


def testClearWasCalled(self):
   aw = aps.Request("nv1")
   with patch.object(aw, 'Clear') as mock:
       aw2 = aps.Request("nv2", aw)

   mock.assert_called_with(42) # or mock.assert_called_once_with(42)

Mock prend en charge un certain nombre de fonctionnalités utiles, y compris des méthodes pour patcher un objet ou un module, ainsi que pour vérifier que la bonne chose a été appelée, etc. etc.

Caveat emptor! (Attention à l'acheteur!)

Si vous vous trompez assert_called_withassert_called_once ou assert_called_wiht) votre test peut toujours être exécuté, car Mock pensera que c'est une fonction fictive et ira de l'avant, sauf si vous utilisez autospec=true. Pour plus d'informations, lisez assert_called_once: Menace ou menace .

113
Macke

Oui si vous utilisez Python 3.3+. Vous pouvez utiliser la fonction intégrée unittest.mock pour affirmer la méthode appelée. Pour Python 2.6+ utiliser le backport roulant Mock , ce qui revient au même.

Voici un exemple rapide dans votre cas:

from unittest.mock import MagicMock
aw = aps.Request("nv1")
aw.Clear = MagicMock()
aw2 = aps.Request("nv2", aw)
assert aw.Clear.called
21
Devy

Je ne suis au courant de rien. C'est assez simple à mettre en œuvre:

class assertMethodIsCalled(object):
    def __init__(self, obj, method):
        self.obj = obj
        self.method = method

    def called(self, *args, **kwargs):
        self.method_called = True
        self.orig_method(*args, **kwargs)

    def __enter__(self):
        self.orig_method = getattr(self.obj, self.method)
        setattr(self.obj, self.method, self.called)
        self.method_called = False

    def __exit__(self, exc_type, exc_value, traceback):
        assert getattr(self.obj, self.method) == self.called,
            "method %s was modified during assertMethodIsCalled" % self.method

        setattr(self.obj, self.method, self.orig_method)

        # If an exception was thrown within the block, we've already failed.
        if traceback is None:
            assert self.method_called,
                "method %s of %s was not called" % (self.method, self.obj)

class test(object):
    def a(self):
        print "test"
    def b(self):
        self.a()

obj = test()
with assertMethodIsCalled(obj, "a"):
    obj.b()

Cela nécessite que l'objet lui-même ne modifie pas self.b, ce qui est presque toujours vrai.

13
Glenn Maynard

Oui, je peux vous donner un aperçu, mais mon Python est un peu rouillé et je suis trop occupé pour expliquer en détail.

En gros, vous devez mettre un proxy dans la méthode qui appellera l'original, par exemple:

 class fred(object):
   def blog(self):
     print "We Blog"


 class methCallLogger(object):
   def __init__(self, meth):
     self.meth = meth

   def __call__(self, code=None):
     self.meth()
     # would also log the fact that it invoked the method

 #example
 f = fred()
 f.blog = methCallLogger(f.blog)

Ceci réponse StackOverflow à propos de callable peut vous aider à comprendre ce qui précède.

Plus en détail:

Bien que la réponse ait été acceptée, en raison de la discussion intéressante avec Glenn et de la gratuité de quelques minutes, je souhaitais développer ma réponse:

# helper class defined elsewhere
class methCallLogger(object):
   def __init__(self, meth):
     self.meth = meth
     self.was_called = False

   def __call__(self, code=None):
     self.meth()
     self.was_called = True

#example
class fred(object):
   def blog(self):
     print "We Blog"

f = fred()
g = fred()
f.blog = methCallLogger(f.blog)
g.blog = methCallLogger(g.blog)
f.blog()
assert(f.blog.was_called)
assert(not g.blog.was_called)
6
Andy Dent

Vous pouvez simuler aw.Clear Manuellement ou à l'aide d'un framework de test tel que pymox . Manuellement, vous le feriez en utilisant quelque chose comme ceci:

class MyTest(TestCase):
  def testClear():
    old_clear = aw.Clear
    clear_calls = 0
    aw.Clear = lambda: clear_calls += 1
    aps.Request('nv2', aw)
    assert clear_calls == 1
    aw.Clear = old_clear

En utilisant pymox, vous le feriez comme ceci:

class MyTest(mox.MoxTestBase):
  def testClear():
    aw = self.m.CreateMock(aps.Request)
    aw.Clear()
    self.mox.ReplayAll()
    aps.Request('nv2', aw)
4
Max Shawabkeh