Giriş argümanlarına dayalı alaycı python işlevi


164

Mock'u bir süredir python için kullanıyoruz .

Şimdi, bir işlevle dalga geçmek istediğimiz bir durum var

def foo(self, my_param):
    #do something here, assign something to my_result
    return my_result

Normalde, bununla dalga geçmenin yolu (foo'nun bir nesnenin parçası olduğu varsayılırsa)

self.foo = MagicMock(return_value="mocked!")

Foo () 'yu birkaç kez çağırsam bile kullanabilirim

self.foo = MagicMock(side_effect=["mocked once", "mocked twice!"])

Şimdi, girdi parametresi belirli bir değere sahip olduğunda sabit bir değer döndürmek istediğim bir durumla karşı karşıyayım. Yani "my_param", "bir şeye" eşitse, "my_cool_mock" döndürmek istiyorum

Bu , python için mockito'da mevcut gibi görünüyor

when(dummy).foo("something").thenReturn("my_cool_mock")

Başarısızlıkla Mock ile aynı şeyi nasıl başaracağımı araştırıyordum?

Herhangi bir fikir?


2
Bu cevap yardımcı olabilir mi? - stackoverflow.com/a/7665754/234606
naiquevin

@naiquevin Bu problemi mükemmel bir şekilde çözüyor dostum, teşekkürler!
Juan Antonio Gomez Moriano

Mocktio'yu Python ile kullanabileceğinizi bilmiyordum, bunun için +1!
Ben

1
Projeniz pytest kullanıyorsa, böyle bir amaçla yararlanmak isteyebilirsiniz monkeypatch. Monkeypatch daha çok "test için bu işlevi değiştirin" anlamına gelirken, Mock, aynı zamanda mock_callsne ile çağrıldığını kontrol etmek veya bunlarla ilgili iddialarda bulunmak istediğinizde kullandığınız şeydir. Her ikisi için de bir yer var ve belirli bir test dosyasında genellikle ikisini de farklı zamanlarda kullanıyorum.
driftcatcher

Yanıtlar:


201

side_effectBir işlev ise , o işlevin döndürdüğü şey, sahte dönüşe yapılan çağrılardır. side_effectİşlev taklidinin aynı argüman olarak adlandırılır. Bu, girdiye bağlı olarak çağrının dönüş değerini dinamik olarak değiştirmenize olanak tanır:

>>> def side_effect(value):
...     return value + 1
...
>>> m = MagicMock(side_effect=side_effect)
>>> m(1)
2
>>> m(2)
3
>>> m.mock_calls
[call(1), call(2)]

http://www.voidspace.org.uk/python/mock/mock.html#calling


27
Yanıtı kolaylaştırmak için side_effect işlevini başka bir şeye yeniden adlandırabilir misiniz? (Biliyorum, biliyorum, oldukça basit, ancak okunabilirliği artırıyor, işlev adı ve parametre adının farklı olması gerçeği :)
Juan Antonio Gomez Moriano

9
@JuanAntonioGomezMoriano Yapabilirim, ancak bu durumda doğrudan dokümantasyondan alıntı yapıyorum, bu yüzden özellikle kırılmamışsa alıntıyı düzenlemekten biraz nefret ediyorum.
Amber

ve sadece bunca yıl sonra bilgiçlikçi olmak için, ama side effectdoğru terim: en.wikipedia.org/wiki/Side_effect_(computer_science)
lsh

7
@ İsminden şikayetçi değiller CallableMixin.side_effect, ancak örnekte tanımlanan ayrı işlevin aynı ada sahip olması.
OrangeDog

58

Birden çok kez çağrılan yöntemle Python Mock nesnesinde belirtildiği gibi

Bir çözüm kendi side_effect'imi yazmaktır

def my_side_effect(*args, **kwargs):
    if args[0] == 42:
        return "Called with 42"
    elif args[0] == 43:
        return "Called with 43"
    elif kwargs['foo'] == 7:
        return "Foo is seven"

mockobj.mockmethod.side_effect = my_side_effect

Bu hile yapar


2
Bu benim için seçilen yanıttan daha net hale getirdi, bu yüzden kendi sorunuzu yanıtladığınız için teşekkür ederim :)
Luca Bezerra

17

Yan etki bir işlev alır (bu aynı zamanda bir lambda işlevi de olabilir ), bu nedenle basit durumlarda kullanabilirsiniz:

m = MagicMock(side_effect=(lambda x: x+1))

4

Burada " girdi argümanlarına dayalı olarak bir işlevle nasıl dalga geçileceğini " aradım ve sonunda bunu basit bir aux işlevi oluşturarak çözdüm :

def mock_responses(responses, default_response=None):
  return lambda input: responses[input] if input in responses else default_response

Şimdi:

my_mock.foo.side_effect = mock_responses(
  {
    'x': 42, 
    'y': [1,2,3]
  })
my_mock.goo.side_effect = mock_responses(
  {
    'hello': 'world'
  }, 
  default_response='hi')
...

my_mock.foo('x') # => 42
my_mock.foo('y') # => [1,2,3]
my_mock.foo('unknown') # => None

my_mock.goo('hello') # => 'world'
my_mock.goo('ey') # => 'hi'

Umarım bu birine yardımcı olur!


2

Parametreleri alan bir işlevi kullanmak istiyorsanız da kullanabilirsiniz partial, functoolsancak alay ettiğiniz işlev bunu yapmaz. Örneğin, bunun gibi:

def mock_year(year):
    return datetime.datetime(year, 11, 28, tzinfo=timezone.utc)
@patch('django.utils.timezone.now', side_effect=partial(mock_year, year=2020))

Bu, parametreleri kabul etmeyen bir çağrılabilir döndürür (Django'nun timezone.now () gibi), ancak mock_year işlevim bunu yapar.


Bu zarif çözüm için teşekkürler. Orijinal işlevinizin ek parametreleri varsa, üretim kodunuzda anahtar kelimelerle çağrılmaları gerektiğini veya bu yaklaşımın işe yaramayacağını eklemek isterim. Sen hatayı alıyorum: got multiple values for argument.
Erik Kalköken

2

Eğer varsa "girdi parametresi özel bir değere sahip olduğunda sabit bir değer döndürmek istiyorum" , belki hatta bir taklidini gerek yoktur ve bir kullanabiliriz dictonun yanında getyöntemle:

foo = {'input1': 'value1', 'input2': 'value2'}.get

foo('input1')  # value1
foo('input2')  # value2

Bu, sahte çıktınızın bir girdi eşlemesi olduğunda iyi çalışır . Bir olduğunda işlev girişinin kullanıyorum öneririm side_effectgereğince Amber 'ın cevabı.

Korumak istiyorsanız da her ikisinin bir kombinasyonunu kullanabilirsiniz Mock(yetenekleri s' assert_called_once, call_countvs):

self.mock.side_effect = {'input1': 'value1', 'input2': 'value2'}.get

1
Bu çok zekice.
emyller

1

Bunu yapmanın başka bir yolunu göstermek için:

def mock_isdir(path):
    return path in ['/var/log', '/var/log/apache2', '/var/log/tomcat']

with mock.patch('os.path.isdir') as os_path_isdir:
    os_path_isdir.side_effect = mock_isdir

1

Ayrıca şunları da kullanabilirsiniz @mock.patch.object:

Bir modülün bir veritabanından okumak için my_module.pykullandığını varsayalım pandasve bu modülü alay pd.read_sql_tableyöntemi ile test etmek istiyoruz ( table_nameargüman olarak alır ).

Yapabileceğiniz şey, (testinizin içinde) db_mocksağlanan argümana bağlı olarak farklı nesneler döndüren bir yöntem oluşturmaktır:

def db_mock(**kwargs):
    if kwargs['table_name'] == 'table_1':
        # return some DataFrame
    elif kwargs['table_name'] == 'table_2':
        # return some other DataFrame

Test fonksiyonunuzda daha sonra şunları yaparsınız:

import my_module as my_module_imported

@mock.patch.object(my_module_imported.pd, "read_sql_table", new_callable=lambda: db_mock)
def test_my_module(mock_read_sql_table):
    # You can now test any methods from `my_module`, e.g. `foo` and any call this 
    # method does to `read_sql_table` will be mocked by `db_mock`, e.g.
    ret = my_module_imported.foo(table_name='table_1')
    # `ret` is some DataFrame returned by `db_mock`
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.