自動(dòng)化測(cè)試中梦湘,我們常會(huì)使用一些經(jīng)過簡(jiǎn)化的瞎颗,行為與表現(xiàn)類似于生產(chǎn)環(huán)境下的對(duì)象的復(fù)制品。引入這樣的復(fù)制品能夠降低構(gòu)建測(cè)試用例的復(fù)雜度捌议,允許我們獨(dú)立而解耦地測(cè)試某個(gè)模塊哼拔,不再擔(dān)心受到系統(tǒng)中其他部分的影響。
在《The Art of Unit Testing》書中Mock 被描述為假對(duì)象瓣颅,通過驗(yàn)證是否發(fā)生與對(duì)象的交互來幫助確定測(cè)試是否失敗或通過倦逐。其他的東西都被定義為Stub。在這本書中宫补,F(xiàn)ake對(duì)象就是不真實(shí)的檬姥,根據(jù)它們的使用情況曾我,它們可以是Stub,也可以是Mock健民。
更復(fù)雜一點(diǎn)的定義是Gerard Meszaros 在XunitPatterns中對(duì)此類對(duì)象的定義抒巢。他對(duì)這類對(duì)象統(tǒng)一稱呼為:Test Double。包含:Dummy秉犹,F(xiàn)ake蛉谜,Spy,Mock和Stub崇堵。
而通常型诚,測(cè)試人員更傾向于使用 Mock 來統(tǒng)一描述不同的 Test Doubles。
不過對(duì)于 Test Doubles 實(shí)現(xiàn)的誤解還是可能會(huì)影響到測(cè)試的設(shè)計(jì)筑辨,使測(cè)試用例變得混亂和脆弱俺驶,最終帶來不必要的重構(gòu)。CC先生就最常用的Mock棍辕,F(xiàn)ake和Stub來解釋一下不同的 Double 的使用場(chǎng)景暮现。
Fake:We use a Fake Object to replace the functionality of a real DOC in a test for reasons other than verification of indirect inputs and outputs of the SUT. Typically, it implements the same functionality as the real DOC but in a much simpler way. While a Fake Object is typically built specifically for testing, it is not used as either a control point or a observation point by the test.
簡(jiǎn)單的來說,F(xiàn)ake 是那些包含了生產(chǎn)環(huán)境下具體實(shí)現(xiàn)的簡(jiǎn)化版本的對(duì)象楚昭。
比如在測(cè)試系統(tǒng)時(shí)需要頻繁的連接數(shù)據(jù)庫進(jìn)行操作栖袋,而此時(shí)有可能數(shù)據(jù)庫還沒有完全實(shí)現(xiàn),我們就可以采用快速編寫系統(tǒng)原型抚太,并且基于內(nèi)存存儲(chǔ)來運(yùn)行整個(gè)系統(tǒng)塘幅,推遲有關(guān)數(shù)據(jù)庫設(shè)計(jì)所用到的一些決定來加速測(cè)試環(huán)境的搭建。另一個(gè)常見的使用場(chǎng)景就是利用 Fake 來保證在測(cè)試環(huán)境下支付永遠(yuǎn)返回成功結(jié)果尿贫。
Stub:Test stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test.
Stub只是返回一個(gè)規(guī)定的值电媳,而不會(huì)去涉及到系統(tǒng)的任何改變。
比較常見的場(chǎng)景就是系統(tǒng)希望去查詢某一類的信息庆亡,而Stub可以總是返回一個(gè)固定值匾乓,比如發(fā)送郵件的功能,Stub可以總是返回郵件發(fā)送成功的標(biāo)識(shí)1又谋,但是你并不知道你到底發(fā)送了郵件給誰或者發(fā)送了幾封郵件拼缝。
Mock:We can use a Mock Object as an observation point that is used to verify the indirect outputs of the SUT as it is exercised. Typically, the Mock Object also includes the functionality of a Test Stub in that it must return values to the SUT if it hasn't already failed the tests but the emphasisis on the verification of the indirect outputs. Therefore, a Mock Object is lot more than just a Test Stub plus assertions; it is used a fundamentally different way.
就算在Gerard Meszaros的定義里面我們可以看出Mock和Stub有一定的重合性,比較大的區(qū)別是Mock專注于observation point彰亥,而Stub專注于control point咧七,或者從另一個(gè)角度上面來說,Mock是會(huì)有行為的更改任斋,而Stub只是狀態(tài)的一個(gè)變化而已继阻。
在Python 3.3以前的版本中,需要另外安裝mock模塊,可以使用pip命令來安裝
pip install mock
使用的時(shí)候直接導(dǎo)入即可:
import mock
從Python 3.3開始瘟檩,mock模塊已經(jīng)被合并到標(biāo)準(zhǔn)庫中犬第,被命名為unittest.mock,可以直接import進(jìn)來使用:
from unittest import mock
也就是說我們以后使用Python的時(shí)候不用導(dǎo)入任何的第三方包就可以方便使用Mock來模擬測(cè)試對(duì)象的芒帕。Python中的Mock是非常容易使用,可以說是在unittest中使用最多丰介。 模擬是基于“動(dòng)作 - >斷言”模式背蟆,而不是許多Mock框架使用的“記錄 - >重放”。
Mock的基礎(chǔ)使用
Mock對(duì)象的一般用法是這樣的:
- 找到你要替換的對(duì)象哮幢,這個(gè)對(duì)象可以是一個(gè)類带膀,或者是一個(gè)函數(shù),或者是一個(gè)類實(shí)例橙垢。
- 實(shí)例化Mock類得到一個(gè)mock對(duì)象垛叨,并且設(shè)置這個(gè)mock對(duì)象的行為,比如被調(diào)用的時(shí)候返回什么值柜某,被訪問成員的時(shí)候返回什么值等嗽元。
- 使用這個(gè)mock對(duì)象替換掉我們想替換的對(duì)象,也就是步驟1中確定的對(duì)象喂击。
之后就可以開始寫測(cè)試代碼剂癌,這個(gè)時(shí)候我們可以保證我們替換掉的對(duì)象在測(cè)試用例執(zhí)行的過程中行為和我們預(yù)設(shè)的一樣。
舉個(gè)例子: 簡(jiǎn)單定義一個(gè)Person類翰绊,其中的代碼為:
class Person:
def __init__(self):
self.__age = 10
def get_fullname(self, first_name, last_name):
return first_name + ' ' + last_name
def get_age(self):
return self.__age
@staticmethod
def get_class_name():
return Person.__name__
類里有兩個(gè)成員方法佩谷,一個(gè)有參數(shù),一個(gè)無參數(shù)监嗜,還有一個(gè)靜態(tài)方法
1). 使用Mock類谐檀,返回固定值
新建一個(gè)文件叫MockPerson.py,來測(cè)試:
from unittest import mock
import unittest
from .person import Person
class PersonTest(unittest.TestCase):
def test_should_get_age(self):
p = Person()
# 不mock時(shí)裁奇,get_age應(yīng)該返回10
self.assertEqual(p.get_age(), 10)
# mock掉get_age方法桐猬,讓它返回20
p.get_age = mock.Mock(return_value=20)
self.assertEqual(p.get_age(), 20)
def test_should_get_fullname(self):
p = Person()
# mock掉get_fullname,讓它返回'Tracy Cheng'
p.get_fullname = mock.Mock(return_value='Tracy cheng')
self.assertEqual(p.get_fullname(), 'Tracy cheng')
if __name__ == '__main__':
unittest.main()
返回固定值時(shí)框喳,按照我們上面的名詞解釋课幕,算是Stub的一種用法,只是用Mock類來實(shí)現(xiàn)的五垮。
2). 使用side_effect乍惊,依次返回指定值:
class PersonTest(unittest.TestCase):
def test_should_get_age(self):
p = Person()
p.get_age = mock.Mock(side_effect=[10, 11, 12])
self.assertEqual(p.get_age(), 10)
self.assertEqual(p.get_age(), 11)
self.assertEqual(p.get_age(), 12)
get_page()每一次被調(diào)用的時(shí)候都會(huì)到Mock的side_effect中去取一個(gè)值。如果調(diào)用次數(shù)超過了side_effect中的個(gè)數(shù)放仗,程序運(yùn)行時(shí)會(huì)報(bào)錯(cuò)StopIteration润绎。
3). 打算輸出為異常時(shí):
p.get_age = mock.Mock(return_value =30,side_effect=Exception('Boom!'))
self.assertRaises(TypeError,p.get_age)
只要調(diào)用就會(huì)拋出異常。
- 檢驗(yàn)是否調(diào)用
def test_should_validate_method_calling(self):
p = Person()
p.get_fullname = mock.Mock(return_value='Tracy cheng')
# 沒調(diào)用過
p.get_fullname.assert_not_called() # Python 3.5
p.get_fullname('1', '2')
# # 調(diào)用過任意次數(shù)
# p.get_fullname.assert_called() # Python 3.6
# # 只調(diào)用過一次, 不管參數(shù)
# p.get_fullname.assert_called_once() # Python 3.6
# 只調(diào)用過一次,并且符合指定的參數(shù)
p.get_fullname.assert_called_once_with('1', '2')
p.get_fullname('3', '4')
# 只要調(diào)用過即可莉撇,必須指定參數(shù)
p.get_fullname.assert_any_call('1', '2')
# 重置mock呢蛤,重置之后相當(dāng)于沒有調(diào)用過
p.get_fullname.reset_mock()
p.get_fullname.assert_not_called()
# Mock對(duì)象里除了return_value, side_effect屬性外,
# called表示是否調(diào)用過棍郎,call_count可以返回調(diào)用的次數(shù)
self.assertEqual(p.get_fullname.called, False)
self.assertEqual(p.get_fullname.call_count, 0)
p.get_fullname('1', '2')
p.get_fullname('3', '4')
self.assertEqual(p.get_fullname.called, True)
self.assertEqual(p.get_fullname.call_count, 2)
其中的assert_called和assert_called_once是python3.6中的用法其障,注意一下Python的版本。
稍微高階一丟丟的用法:
靜態(tài)方法和模塊方法需要用到Patch來mock涂佃。其中會(huì)用到Patch裝修器荧琼,包含有: patch(), patch.object() and patch.dict().
patch和patch.object這兩個(gè)函數(shù)都會(huì)返回一個(gè)mock內(nèi)部的類實(shí)例尝哆,這個(gè)類是class _patch。返回的這個(gè)類實(shí)例既可以作為函數(shù)的裝飾器,也可以作為類的裝飾器苟翻,也可以作為上下文管理器伦泥。使用patch或者patch.object的目的是為了控制mock的范圍柜候,意思就是在一個(gè)函數(shù)范圍內(nèi)明场,或者一個(gè)類的范圍內(nèi),或者with語句的范圍內(nèi)mock掉一個(gè)對(duì)象午笛。
# 在patch中給出定義好的Mock的對(duì)象惭蟋,好處是定義好的對(duì)象可以復(fù)用
def test_should_get_class_name(self):
mock_get_class_name = mock.Mock(return_value='Man')
with mock.patch.object(Person,'get_class_name',mock_get_class_name):
self.assertEqual('Man',Person.get_class_name())
當(dāng)你知道了mock能做什么之后,要如何學(xué)習(xí)并掌握mock呢季研?最好的方式就是查看閱讀官方文檔敞葛,并在自己的單元測(cè)試中使用。
也有一些大神已經(jīng)封裝出更好使用的第三方Python Mock庫与涡,可參見:
Python中好用的第三方mock庫-httmock
拓展:
Python3 mock模塊 -Python官網(wǎng)
Test Double - Martin Fowler
Test Double - xUnit Patterns
Mocks Aren't Stubs - Martin Fowler
Command Query Separation - Martin Fowler