Python mock 模組 - 淺談 spec, return_value, side_effect, wraps - Part 2
Posted on Dec 17, 2018 in Python 程式設計 - 高階 by Amo Chen ‐ 2 min read
本文為 unittest.mock 系列文章:
- Python mock 模組 - 淺談 spec, return_value, side_effect, wraps - Part 1
- Python mock 模組 - 淺談 spec, return_value, side_effect, wraps - Part 2
Python mock 模組 - 淺談 spec, return_value, side_effect, wraps - Part 1 中介紹 unittest.mock 模組中的 return_value
, side_effect
概念與用法,本文將接續介紹 spec
與 wraps
。
spec
預設的 Mock / MagicMock 其實存取任何屬性(甚至根本不存在的屬性)、呼叫任何方法都會回傳 1 個 mock ,雖然使用上相當方便,但是這樣的行為表現就顯得不真實。
因此 Mock 也提供 spec
參數, spec 也就是規格的意思,其功用是讓 mock 盡可能地按照規格模仿,試圖取得不在規格內的屬性或者方法,就拋出 AttributeError
,如此一來可避免 mock 預設的特性造成的困擾,測試案例的 mock 就更加嚴謹。
目前 spec
參數可代入 object 或者都是字串的 list 。如果是 object , mock 就會自行解讀該 object 內的屬性與方法,如果是字串 list ,那麼每一個字串元素都會被視為 mock 中的屬性,可以參考範例。
以下為預設 Mock 與有使用 spec
的 Mock 比較,可以發現沒有 spec
的情況下, mock 存取任意屬性與呼叫任何方法都不會有問題,而有使用 spec
的 Mock 遇到 spec
不支援的屬性或方法時就會拋出 AttributeError
:
from unittest.mock import Mock
mock_without_spec = Mock()
mock_without_spec.attribute_a
mock_without_spec.attribute_b
mock_without_spec.print_attribute_a()
mock_without_spec.print_attribute_b()
class TheSpec(object):
NAME = 'spec'
def __init__(self):
self.attribute_a = 'a'
def print_attribute_a(self):
print(self.attribute_a)
mock_with_spec = Mock(spec=TheSpec())
mock_with_spec.attribute_a
mock_with_spec.attribute_b # AttributeError
mock_with_spec.print_attribute_a()
mock_with_spec.print_attribute_b() # AttributeError
mock_with_spec = Mock(spec=['NAME', 'attribute_a', 'print_attribute_a'])
mock_with_spec.NAME
mock_with_spec.print_attribute_a()
mock_with_spec.attribute_b # AttributeError
spec_set
談完 spec
就能夠進一步理解 spec_set
。
有使用 spec
的 mock 試圖取得(get)不在規格內的屬性時,會拋出 AttributeError
,但還是可以設定(set)不在規格內的屬性或方法。
而使用 spec_set
的情況下,則是連設定(set)不在規格內的屬性或方法時都會拋出 AttributeError
,可以說是相當嚴格的設定。
以下範例直接展示 2 者的差別:
from unittest.mock import Mock
class TheSpec(object):
NAME = 'spec'
def __init__(self):
self.attribute_a = 'a'
def print_attribute_a(self):
print(self.attribute_a)
mock_with_spec = Mock(spec=TheSpec())
mock_with_spec.attribute_b = 'b'
mock_with_spec_set = Mock(spec_set=TheSpec())
mock_with_spec_set.attribute_b = 'b' # AttributeError
wraps
目前為止談到的 mock 都是虛假的 object ,用來取代原本受測程式中的特定 object 。
那麼有沒有一種 mock ,在 mock 的同時還能讓原本模仿的對象實際執行?答案是,有!
這項行為被稱為 wraps ,簡單說就是用 mock 將模仿的對象包起來,在呼叫 mock 的時候, mock 也會以相同的參數呼叫被包起來的 object ,讓該 object 實際執行。
例如以下範例 mock_with_wraps
在呼叫 print_attribute_a('b', 'c')
時,參數 'b', 'c'
被代入 TheSpec
物件中的 print_attribute_a
方法,所以執行結果會印出 a ('b', 'c')
。
from unittest.mock import Mock
class TheSpec(object):
NAME = 'spec'
def __init__(self):
self.attribute_a = 'a'
def print_attribute_a(self, *args):
print(self.attribute_a, args)
return True
mock_with_wraps = Mock(wraps=TheSpec())
assert mock_with_wraps.print_attribute_a('b', 'c') is True # will print a ('b', 'c')
這就是 wraps
的功用。
需要注意的是同時設定 return_value
與 wraps
的話, mock 會忽略 wraps
的設定,直接傳回 return_value
。
If the mock has an explicit return_value set then calls are not passed to the wrapped object and the return_value is returned instead.
總結
經過 2 篇文章說明之後,相信大家對於 mock 模組中的 spec, return_value, side_effect, wraps 也不會過於陌生才是,未來在撰寫測試案例(test case)時,也一定能夠使用合適的設定。
總之, Happy Coding!
References
https://docs.python.org/3/library/unittest.mock.html