Bir Python birim testinde bir yöntemin çağrıldığını iddia edin


91

Bir Python birim testinde aşağıdaki koda sahip olduğumu varsayalım:

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

aw.Clear()Testin ikinci satırında belirli bir yöntemin (benim durumumda ) çağrıldığını iddia etmenin kolay bir yolu var mı ? örneğin şuna benzer bir şey var mı:

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

Yanıtlar:


150

Kullandığım Mock (şimdi unittest.mock bunun için py3.3 + 'da):

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)

Davanız için şöyle görünebilir:

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, bir nesneyi veya modülü düzeltme yollarının yanı sıra doğru şeyin çağrıldığını kontrol etme vb. Dahil olmak üzere birkaç yararlı özelliği destekler.

Uyarı imparatoru! (Alıcı dikkatli olun!)

Yanlış yazarsanız assert_called_with( assert_called_onceveya assert_called_wiht) testiniz yine de çalışabilir, çünkü Mock bunun alay konusu bir işlev olduğunu düşünür ve siz kullanmadığınız sürece mutlu bir şekilde devam eder autospec=true. Daha fazla bilgi için assert_called_once: Threat or Menace sayfasını okuyun .


5
Harika Mock modülüyle dünyamı ayrı ayrı aydınlatmak için +1.
Ron Cohen

@RonCohen: Evet, oldukça şaşırtıcı ve her zaman daha iyi hale geliyor. :)
Macke

1
Sahte kullanmak kesinlikle doğru yol olsa da, assert_called_once kullanılmamasını tavsiye ederim, sadece mevcut değil :)
FelixCQ

sonraki sürümlerde kaldırılmıştır. Testlerim hala kullanıyor. :)
Macke

1
Alay edilen herhangi bir nesne için autospec = True kullanmanın ne kadar yararlı olduğunu tekrarlamakta fayda var çünkü assert yöntemini yanlış yazarsanız sizi gerçekten ısırabilir.
rgilligan

30

Python 3.3+ kullanıyorsanız evet. unittest.mockÇağrılan yöntemi onaylamak için yerleşik kullanabilirsiniz . Python 2.6+ Mockiçin aynı şey olan geri dönen arka portu kullanın .

İşte sizin durumunuz için hızlı bir örnek:

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

14

Yerleşik hiçbir şeyin farkında değilim. Uygulaması oldukça basit:

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

Bu, nesnenin kendisinin self.b'yi değiştirmemesini gerektirir ki bu neredeyse her zaman doğrudur.


Python'umun paslı olduğunu söyledim, ancak çözümümü çalıştığından emin olmak için test etsem :-) Sürüm 2.5'ten önce Python'u içselleştirdim, aslında lib uyumluluğu için 2.3'te dondurmamız gerektiğinden hiçbir önemli Python için 2.5 kullanmadım. Çözümünüzü incelerken effbot.org/zone/python-with-statement.htm'yi güzel ve net bir açıklama olarak buldum . Alçakgönüllülükle yaklaşımımın daha küçük göründüğünü ve "ile" iç içe geçmiş birden fazla günlük kaydı istiyorsanız uygulamanın daha kolay olabileceğini öneririm. Herhangi bir özel faydanız olup olmadığını açıklamanızı gerçekten çok isterim.
Andy Dent

@Andy: Cevabınız daha küçük çünkü kısmi: aslında sonuçları test etmiyor, testten sonra orijinal işlevi geri yüklemiyor, böylece nesneyi kullanmaya devam edebiliyorsunuz ve her şeyi yapmak için kodu tekrar tekrar yazmanız gerekiyor her seferinde bir test yazdığınızda. Destek kodunun satır sayısı önemli değildir; bu sınıf bir docstring'de satır içi değil, kendi test modülüne gider - bu, gerçek testte bir veya iki satır kod alır.
Glenn Maynard

6

Evet, size taslağı verebilirim ama Python'um biraz paslı ve ayrıntılı olarak açıklayamayacak kadar meşgulüm.

Temel olarak, orijinali çağıracak yönteme bir proxy koymanız gerekir, örneğin:

 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)

Çağrılabilir hakkındaki bu StackOverflow yanıtı , yukarıdakileri anlamanıza yardımcı olabilir.

Daha ayrıntılı olarak:

Cevap kabul edilmiş olsa da, Glenn'le olan ilginç tartışma ve birkaç dakikam boş olduğu için cevabımı genişletmek istedim:

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

Güzel. MethCallLogger'a bir çağrı sayısı ekledim, böylece bunu iddia edebilirim.
Mark Heath

Bu, sağladığım kapsamlı, bağımsız çözüm üzerinden mi? Ciddi anlamda?
Glenn Maynard

@Glenn Python'da çok yeniyim - belki seninki daha iyi - henüz hepsini anlamadım. Daha sonra denemek için biraz zaman harcayacağım.
Mark Heath

Bu açık arayla en basit ve anlaşılması en kolay cevaptır. Gerçekten güzel iş!
Matt Messersmith

4

aw.ClearManuel olarak veya pymox gibi bir test çerçevesi kullanarak alay edebilirsiniz . Manuel olarak, şunun gibi bir şey kullanarak yaparsınız:

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

Pymox kullanarak bunu şöyle yaparsınız:

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

Bu yaklaşımı da seviyorum, ancak yine de old_clear'ın çağrılmasını istiyorum. Bu, neler olduğunu açıkça ortaya koyuyor.
Mark Heath
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.