Sahte bir salt okunur mülkle nasıl dalga geçilir?


92

Nasıl bir Salt okunur özelliği alay ediyorsun mock ?

Denedim:

setattr(obj.__class__, 'property_to_be_mocked', mock.Mock())

ancak sorun şu ki, sınıfın tüm örnekleri için geçerli olması ... bu da testlerimi bozuyor.

Başka bir fikrin var mı Tam nesneyle alay etmek istemiyorum, sadece bu belirli özellik.

Yanıtlar:


167

Bence daha iyi yol, doğrudan yöntemle PropertyMockdalga geçmektense, mülkle dalga geçmek __get__.

Bu belirtilmiştir belgeler için arama unittest.mock.PropertyMock: A taklidinin bir sınıf üzerinde bir mülkiyet veya diğer tanımlayıcı olarak kullanılması amaçlanmıştır. PropertyMocksağlar __get__ve __set__yöntemler, böylece getirildiğinde bir dönüş değeri belirtebilirsiniz.

İşte nasıl:

class MyClass:
    @property
    def last_transaction(self):
        # an expensive and complicated DB query here
        pass

def test(unittest.TestCase):
    with mock.patch('MyClass.last_transaction', new_callable=PropertyMock) as mock_last_transaction:
        mock_last_transaction.return_value = Transaction()
        myclass = MyClass()
        print myclass.last_transaction
        mock_last_transaction.assert_called_once_with()

Olarak dekore edilmiş bir sınıf yöntemiyle dalga geçmek zorunda kaldım @property. Diğer cevap (ve diğer birçok soruya verilen diğer cevaplar) işe yaramadığında bu cevap benim için işe yaradı.
AlanSE

3
yapılması gereken budur. Keşke "kabul edilen" yanıtı değiştirmenin bir yolu olsaydı
vitiral

4
Bağlam yöneticisi çağrısına dönüş değerini dahil etmeyi biraz daha temiz buluyorum: `` ile mock.patch ('MyClass.last_transaction', new_callable = PropertyMock, return_value = Transaction ()): ... ``
wodow

Doğrusu, kabul edilen cevabı buna kaydırdım.
charlax

1
mock.patch.object'i kullanmak da güzeldir çünkü sınıf adını bir dizge olarak yazmanız gerekmez (örnekte gerçekten bir sorun değildir) ve bir paketi yeniden adlandırmaya karar verirseniz ve yapmadıysanız tespit etmek / düzeltmek daha kolaydır. bir testi güncelledi
Kevin

41

Aslında, cevap (her zamanki gibi) dokümantasyondaydı , sadece yamayı, örneğini takip ettiğimde sınıf yerine örneğe uyguluyordum.

İşte nasıl yapılacağı:

class MyClass:
    @property
    def last_transaction(self):
        # an expensive and complicated DB query here
        pass

Test paketinde:

def test():
    # Make sure you patch on MyClass, not on a MyClass instance, otherwise
    # you'll get an AttributeError, because mock is using settattr and
    # last_transaction is a readonly property so there's no setter.
    with mock.patch(MyClass, 'last_transaction') as mock_last_transaction:
        mock_last_transaction.__get__ = mock.Mock(return_value=Transaction())
        myclass = MyClass()
        print myclass.last_transaction

14
insanlar diğer örneği kullanmalıdır. mock.PropertyMockbunu yapmanın yolu!
vitiral

4
Bu doğru, yazma sırasında PropertyMockyoktu.
charlax

6

Özelliğini geçersiz kılmak istediğiniz nesne sahte bir nesneyse, kullanmanız gerekmez patch.

Bunun yerine, bir oluşturabilir PropertyMockve ardından özelliği modelin türüne göre geçersiz kılabilir . Örneğin, mock_rows.pagesdöndürülecek özelliği geçersiz kılmak için (mock_page, mock_page,):

mock_page = mock.create_autospec(reader.ReadRowsPage)
# TODO: set up mock_page.
mock_pages = mock.PropertyMock(return_value=(mock_page, mock_page,))
type(mock_rows).pages = mock_pages

1
Bam, tam da istediğim şey (bir özelliği olan otomatik tanımlı nesne). Ve bir meslektaştan daha az değil 🙋‍♂️
Mark McDonald

6

Muhtemelen bir tarz meselesi ama testlerde dekoratörleri tercih etmeniz durumunda @ jamescastlefield'ın cevabı şu şekilde değiştirilebilir:

class MyClass:
    @property
    def last_transaction(self):
        # an expensive and complicated DB query here
        pass

class Test(unittest.TestCase):
    @mock.patch('MyClass.last_transaction', new_callable=PropertyMock)
    def test(self, mock_last_transaction):
        mock_last_transaction.return_value = Transaction()
        myclass = MyClass()
        print myclass.last_transaction
        mock_last_transaction.assert_called_once_with()

6

Eğer kullandığınız pytestbirlikte pytest-mocksen, yani kodunuzu basitleştirmek ve aynı zamanda bağlam yemlik kullanarak önleyebilirsiniz, withşöyle açıklama:

def test_name(mocker): # mocker is a fixture included in pytest-mock
    mocked_property = mocker.patch(
        'MyClass.property_to_be_mocked',
        new_callable=mocker.PropertyMock,
        return_value='any desired value'
    )
    o = MyClass()

    print(o.property_to_be_mocked) # this will print: any desired value

    mocked_property.assert_called_once_with()

0

Alay konusu özelliğe erişilip erişilmediğini test etmek istemiyorsanız, beklenen özellik ile yama yapabilirsiniz return_value.

with mock.patch(MyClass, 'last_transaction', Transaction()):
    ...

0

@propertyOrijinaline güvenmek için alayınıza ihtiyacınız varsa __get__, kendi özelMockProperty

class PropertyMock(mock.Mock):

    def __get__(self, obj, obj_type=None):
        return self(obj, obj_type)

Kullanım:

class A:

  @property
  def f(self):
    return 123


original_get = A.f.__get__

def new_get(self, obj_type=None):
  return f'mocked result: {original_get(self, obj_type)}'


with mock.patch('__main__.A.f', new_callable=PropertyMock) as mock_foo:
  mock_foo.side_effect = new_get
  print(A().f)  # mocked result: 123
  print(mock_foo.call_count)  # 1
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.