簡體   English   中英

在自動指定時模擬side_effect給函數附加參數

[英]Mocking side_effect while autospeccing gives the function additional argument

因此,示例代碼非常基礎:

@mock.patch.object(BookForm, 'is_valid')
def test_edit(self, mocked_is_valid):
    create_superuser()
    self.client.login(username="test", password="test")

    book = Book()
    book.save()

    mocked_is_valid.side_effect = lambda: True

    self.client.post(reverse('edit', args=[book.pk]), {})

這很好。 但是在模擬中添加autospec關鍵字:

@mock.patch.object(BookForm, 'is_valid', autospec=True)

導致將其他參數傳遞給side_effect可調用對象,這顯然會導致錯誤:

TypeError: <lambda>() takes 0 positional arguments but 1 was given

我不理解的是為什么自動指定提供了其他參數。 我已經閱讀了文檔 ,但仍然找不到這種行為的解釋。

從理論上講,

另外,模擬的函數/方法具有與原始函數相同的調用簽名,因此如果調用不正確,則會引發TypeError。

這樣就可以了( is_validself參數,這可能是在此處傳遞的參數),但另一方面,它也寫了有關side_effect

該函數使用與模擬相同的參數調用,除非返回DEFAULT,否則將此函數的返回值用作返回值。

據我了解,即使沒有selfside_effect應使用self參數調用side_effect。 但事實並非如此。

用與模擬相同的參數調用

if form.is_valid():  # the mock is_valid is called with the self argument, isn't it?

因此,如果有人可以向我解釋,最好是引用文檔,我將不勝感激。

您誤解了文檔。 如果沒有autospecside_effect調用的side_effect實際上不會檢查原始聲明。 讓我們創建一個更好的最小示例來演示此問題。

class Book(object):
    def __init__(self):
        self.valid = False
    def save(self):
        self.pk = 'saved'
    def make_valid(self):
        self.valid = True

class BookForm(object):
    def __init__(self, book):
        self.book = book
    def is_valid(self):
        return self.book.valid

class Client(object):
    def __init__(self, book):
        self.form = BookForm(book)
    def post(self):
        if self.form.is_valid() is True:  # to avoid sentinel value
            print('Book is valid')
        else:
            print('Book is invalid')

現在,您的原始測試應該可以進行一些調整,大致相同

@mock.patch.object(BookForm, 'is_valid')
def test_edit(mocked_is_valid):
    book = Book()
    book.save()
    client = Client(book)
    mocked_is_valid.side_effect = lambda: True
    client.post()

按原樣運行測試將導致Book is valid打印到stdout,即使我們沒有經歷過將Book.valid標志設置為true的情況,因為在self.form.is_valid中調用了Client.post與被調用的lambda。 我們可以通過調試器看到這一點:

> /usr/lib/python3.4/unittest/mock.py(962)_mock_call()
-> ret_val = effect(*args, **kwargs)
(Pdb) pp effect
<function test_edit.<locals>.<lambda> at 0x7f021dee6730>
(Pdb) bt
...
  /tmp/test.py(20)post()
-> if self.form.is_valid():
  /usr/lib/python3.4/unittest/mock.py(896)__call__()
-> return _mock_self._mock_call(*args, **kwargs)
  /usr/lib/python3.4/unittest/mock.py(962)_mock_call()
-> ret_val = effect(*args, **kwargs)

同樣在Client.post方法調用的框架內,它也不是綁定方法(我們稍后會Client.post

(Pdb) self.form.is_valid
<MagicMock name='is_valid' id='140554947029032'>

因此,在這里,我們可能會遇到一個問題: side_effect實際上可以是與實際情況不同的任何可調用對象,在本例中, is_valid函數簽名(即參數列表)可能與我們提供的模擬不同。 如果修改BookForm.is_valid方法以接受其他參數怎么BookForm.is_valid

class BookForm(object):
    def __init__(self, book):
        self.book = book
    def is_valid(self, authcode):
        return authcode > 0 and self.book.valid

重新運行我們的測試...,即使Client.post仍在不帶任何參數的情況下調用BookForm.is_valid ,您將看到我們的測試已通過 即使您的測試已通過,您的產品也將無法生產。 這就是引入autospec參數的原因,我們將在第二個測試中應用該參數,而不用通過side_effect替換可調用對象:

@mock.patch.object(BookForm, 'is_valid', autospec=True)
def test_edit_autospec(mocked_is_valid):
    book = Book()
    book.save()
    client = Client(book)
    client.post()

現在,在調用函數時會發生這種情況

Traceback (most recent call last):
  ...
  File "/tmp/test.py", line 49, in test_edit_autospec
    client.post()
  File "/tmp/test.py", line 20, in post
    if self.form.is_valid():
  ...
  File "/usr/lib/python3.4/inspect.py", line 2571, in _bind
    raise TypeError(msg) from None
TypeError: 'authcode' parameter lacking default value

您想要的是什么以及autospec打算提供的內容-在調用autospec之前進行檢查,以及

另外,模擬的函數/方法具有與原始函數相同的調用簽名,因此如果調用不正確,則會引發TypeError。

因此,我們必須通過提供大於0的authcode來修復Client.post方法。

    def post(self):
        if self.form.is_valid(123) is True:
            print('Book is valid')
        else:
            print('Book is invalid')

由於我們的測試沒有通過side_effect調用來模擬is_valid函數,因此該方法將最終打印Book is invalid

現在,如果我們要提供side_effect ,則必須匹配相同的簽名

@mock.patch.object(BookForm, 'is_valid', autospec=True)
def test_edit_autospec(mocked_is_valid):
    book = Book()
    book.save()
    client = Client(book)
    mocked_is_valid.side_effect = lambda self, authcode: True
    client.post()

Book is valid現在將再次打印。 通過調試器將檢查該autospec “d和嘲笑is_valid對象的的框架內Client.post方法調用

(Pdb) self.form.is_valid
<bound method BookForm.is_valid of <__main__.BookForm object at 0x7fd57f43dc88>>

嗯,以某種方式,方法簽名不是一個簡單的MagicMock對象(記得<MagicMock name='is_valid' id='140554947029032'><MagicMock name='is_valid' id='140554947029032'> ),並且是一個正確綁定的方法,這意味着self參數現在已傳遞給模擬,解決這個問題:

side_effect :每當調用Mock時都要調用的函數。 請參閱side_effect屬性。 對於引發異常或動態更改返回值很有用。 使用與模擬相同的參數調用該函數。

在這種情況下,“與模擬參數相同”的含義與傳遞給模擬的參數相同。 重申一下,第一種情況將self.form.is_valid替換為self.form.is_valid的裸露可調用對象,因此self不會被傳遞; 在第二種情況下,可調用對象現在已綁定到selfselfauthcode都將傳遞到side_effect可調用對象中-就像在實際調用中會發生的那樣。 這應該可以解決對autospec=True的交互行為的不當行為,其中對於mock.patch.object ,使用autospec=True進行交互;對於人工調用,可以手動定義side_effect

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM