Python接口自動化之mock模塊簡單使用

mock簡介

? ? ? ? mock原是python的第三方庫拴测,python 2可以直接安裝mock模塊糊余,但在python 3.3以后mock模塊已經(jīng)整合到了unittest測試框架中逞壁,不需要再單獨安裝。

????????Mock這個詞在英語中有模擬的這個意思浅悉,因此我們可以猜測出這個庫的主要功能是模擬一些東西胸嘴。準確的說,Mock是Python中一個用于支持單元測試的庫嵌莉,它的主要功能是使用mock對象替代掉指定的Python對象进萄,以達到模擬對象的行為。簡單的說锐峭,mock庫用于如下的場景:

假設你開發(fā)的一個api在工作的時候需要調(diào)用發(fā)送請求給特定的服務器來得到一個JSON返回值中鼠,然后根據(jù)這個返回值來做處理。如果要為該API寫一個單元測試沿癞,該如何做援雇?

一個簡單的辦法是搭建一個測試的服務器,在單元測試的時候椎扬,讓該API和這個測試服務器交互惫搏。但是這種做法有兩個問題:

1、測試服務器可能很不好搭建蚕涤,或者搭建效率很低筐赔。

2、你搭建的測試服務器可能無法返回所有可能的值揖铜,或者需要大量的工作才能達到這個目的茴丰。

那么如何在沒有測試服務器的情況下進行上面這種情況的單元測試呢?Mock模塊就是答案。因為mock模塊可以替換Python對象贿肩,返回值能夠由我們的mock對象來決定峦椰,而不需要服務器的參與




mock作用:

1. 解決依賴問題:當我們測試一個接口或者功能模塊的時候汰规,如果這個接口或者功能模塊依賴其他接口或其他模塊汤功,那么如果所依賴的接口或功能模塊未開發(fā)完畢,那么我們就可以使用mock模擬被依賴接口溜哮,完成目標接口的測試

2. 單元測試:如果某個功能未開發(fā)完成滔金,我們又要進行測試用例的代碼編寫,我們也可以先模擬這個功能進行測試

3. 模擬復雜業(yè)務的接口:實際工作中如果我們在測試一個接口功能時茂嗓,如果這個接口依賴一個非常復雜的接口業(yè)務鹦蠕,那么我們完全可以使用mock來模擬這個復雜的業(yè)務接口,其實這個和解決接口依賴是一樣的原理

4.前后端聯(lián)調(diào):如果你是一個前端頁面開發(fā)在抛,現(xiàn)在需要開發(fā)一個功能:根據(jù)后臺返回的狀態(tài)展示不同的頁面赫蛇,那么你就需要調(diào)用后臺的接口授翻,但是后臺接口還未開發(fā)完成,是不是你就停止這部分工作呢测僵?答案是否定的票唆,你完全可以借助mock來模擬后臺這個接口返回你想要的數(shù)據(jù)

Mock的安裝和導入

1朴读、在Python 3.3以前的版本中,需要另外安裝mock模塊走趋,可以使用pip命令來安裝:

pip install mock

然后在代碼中就可以直接import進來:

import mock


2衅金、從Python 3.3開始,mock模塊已經(jīng)被合并到標準庫中簿煌,被命名為unittest.mock氮唯,可以直接import進來使用:

from unittest import mock



Mock對象

????Mock對象是mock模塊中最重要的概念。Mock對象就是mock模塊中的一個類的實例姨伟,這個類的實例可以用來替換其他的Python對象惩琉,來達到模擬的效果。Mock類的定義如下:

classMock(spec=None, side_effect=None, return_value=DEFAULT, wraps=None, name=None, spec_set=None, **kwargs)

Mock對象的一般用法是這樣的:

1夺荒、找到你要替換的對象瞒渠,這個對象可以是一個類,或者是一個函數(shù)技扼,或者是一個類實例伍玖。

2、然后實例化Mock類得到一個mock對象剿吻,并且設置這個mock對象的行為窍箍,比如被調(diào)用的時候返回什么值,被訪問成員的時候返回什么值等。

3仔燕、使用這個mock對象替換掉我們想替換的對象造垛,也就是步驟1中確定的對象。

4晰搀、之后就可以開始寫測試代碼五辽,這個時候我們可以保證我們替換掉的對象在測試用例執(zhí)行的過程中行為和我們預設的一樣。


mock實例及基本用法

????一個未開發(fā)完成的功能如何測試外恕?假如們現(xiàn)在有一個實現(xiàn)兩個數(shù)相加的功能需要編寫測試用例杆逗,但是由于開發(fā)進度緩慢,只搭兩個簡單的框架鳞疲,并沒有內(nèi)部實現(xiàn)罪郊。

import unittest

from unittest import mock

class SubClass(object):

? ? def add(self, a, b):

? ? ? ? """兩個數(shù)相加"""

? ? ? ? pass

class TestSub(unittest.TestCase):

  """測試兩個數(shù)相加用例"""

? ? def test_sub(self):

? ? ? ? sub = SubClass()? # 初始化被測函數(shù)類實例

? ? ? ? sub.add = mock.Mock(return_value=10)? # mock add方法 返回10

? ? ? ? result = sub.add(5, 5)? # 調(diào)用被測函數(shù)

? ? ? ? self.assertEqual(result, 10)? # 斷言實際結(jié)果和預期結(jié)果

if __name__ == '__main__':

? ? unittest.main()

測試結(jié)果:

----------------------------------------------------------------------

Ran 1 test in 0.000s

OK

Process finished with exit code?

測試結(jié)果顯示,測試用例執(zhí)行通過了尚洽。

實際上mock模擬add方法的原理是 使用相同的對象方法接收mock的對象(使用sub.add接收)悔橄,那么當mock對象被調(diào)用時(sub.add())就會返回return_value參數(shù)對應的數(shù)據(jù)




稍微高級的用法

1、class Mock的參數(shù)

上面講的是mock對象最基本的用法腺毫。下面來看看mock對象的稍微高級點的用法

先來看看Mock這個類的參數(shù)癣疟,在上面看到的類定義中,我們知道它有好幾個參數(shù)潮酒,這里介紹最主要的幾個:

name: 這個是用來命名一個mock對象睛挚,只是起到標識作用,當你print一個mock對象的時候急黎,可以看到它的name扎狱。

return_value: 這個我們剛才使用過了,這個字段可以指定一個值(或者對象)勃教,當mock對象被調(diào)用時淤击,如果side_effect函數(shù)返回的是DEFAULT,則對mock對象的調(diào)用會返回return_value指定的值荣回。

side_effect: 這個參數(shù)指向一個可調(diào)用對象遭贸,一般就是函數(shù)。當mock對象被調(diào)用時心软,如果該函數(shù)返回值不是DEFAULT時壕吹,那么以該函數(shù)的返回值作為mock對象調(diào)用的返回值。

上面的例子删铃,我們把用例的代碼修改一句如下:

class TestSub(unittest.TestCase):

? ? """測試兩個數(shù)相加"""

? ? def test_sub(self):

? ? ? ? sub = SubClass()? # 初始化被測函數(shù)類實例

? ? ? ? sub.add = mock.Mock(return_value=10, side_effect=sub.add)? # 傳遞side_effect關(guān)鍵字參數(shù), 會覆蓋return_value參數(shù)值, 使用真實的add方法測試

? ? ? ? result = sub.add(5, 11)? # 真正的調(diào)用被測函數(shù)

? ? ? ? self.assertEqual(result, 16)? # 斷言實際結(jié)果和預期結(jié)果

????????代碼中我們給Mock方法添加了另一個關(guān)鍵字參數(shù)side_effect = sub.add耳贬, 這個參數(shù)和return_value 正好相反,當傳遞這個參數(shù)的時候return_value 參數(shù)就會失效

  而side_effect生效猎唁,這里我給的參數(shù)值是sub.add 相當于add方法的地址咒劲,那么當調(diào)用add方法時就會真實的使用add方法,也就達到了我們測試實際的add 方法。

  你也可以理解為當傳遞了side_effect參數(shù)且值為被測方法地址時腐魂,mock就不會起作用

  side_effect接收的是一個可迭代序列帐偎,當傳遞多個值時,那么每次調(diào)用mock時會返回不同的值

mock_obj = mock.Mock(side_effect= [1,2,3])

print(mock_obj())

print(mock_obj())

print(mock_obj())

print(mock_obj())

輸出

Traceback (most recent call last):

1

? File "D:/MyThreading/mymock.py", line 37, in <module>

2

? ? print(mock_obj())

3

? File "C:\Python36\lib\unittest\mock.py", line 939, in __call__

? ? return _mock_self._mock_call(*args, **kwargs)

? File "C:\Python36\lib\unittest\mock.py", line 998, in _mock_call

? ? result = next(effect)

StopIteration

Process finished with exit code 1

當所有值被取完后就會報錯(這個地方有點類似生成器的原理)


2蛔屹、mock對象的自動創(chuàng)建

當訪問一個mock對象中不存在的屬性時削樊,mock會自動建立一個子mock對象,并且把正在訪問的屬性指向它兔毒,這個功能對于實現(xiàn)多級屬性的mock很方便漫贞。

client= mock.Mock()client.v2_client.get.return_value ='200'

這個時候,你就得到了一個mock過的client實例育叁,調(diào)用該實例的v2_client.get()方法會得到的返回值是"200"迅脐。

從上面的例子中還可以看到,指定mock對象的return_value還可以使用屬性賦值的方法豪嗽。




存在依賴關(guān)系的功能如何測試谴蔑?

? ? ? ?假設有這樣一個場景:我們要測試一個支付接口但是這個支付接口又依賴一個第三方支付接口,那么第三方支付接口我們暫時沒有權(quán)限使用龟梦,那么我們該如何測試我們自己這個接口呢树碱?

  看下面的實例,假設第三方接口和我們自己的支付接口如下:

import requests

class PayApi(object):

? ? @staticmethod

? ? def auth(card, amount):

? ? ? ? """

? ? ? ? 第三方支付接口

? ? ? ? :param card: 卡號

? ? ? ? :param amount: 支付金額

? ? ? ? :return:

? ? ? ? """

? ? ? ? pay_url = "http://www.zhifubao.com"? # 第三方支付接口地址

? ? ? ? data = {"card": card, "amount": amount}

? ? ? ? response = requests.post(pay_url, data=data)? # 請求第三方支付接口

? ? ? ? return response? # 返回狀態(tài)碼

? ? def pay(self, user_id, card, amount):

? ? ? ? """

? ? ? ? 我們自己的支付接口

? ? ? ? :param user_id: 用戶id

? ? ? ? :param card: 卡號

? ? ? ? :param amount: 支付金額

? ? ? ? :return:

? ? ? ? """

     # 調(diào)用第三方支付接口

? ? ? ? response = self.auth(card, amount)

? ? ? ? try:

? ? ? ? ? ? if response['status_code'] == '200':

? ? ? ? ? ? ? ? print('用戶{}支付金額{}成功'.format(user_id, amount))

? ? ? ? ? ? ? ? return '支付成功'

? ? ? ? ? ? elif response['status_code'] == '500':

? ? ? ? ? ? ? ? print('用戶{}支付失敗, 金額不變'.format(user_id))

? ? ? ? ? ? ? ? return '支付失敗'

? ? ? ? ? ? else:

? ? ? ? ? ? ? ? return '未知錯誤'

? ? ? ? except Exception:

? ? ? ? ? ? return "Error, 服務器異常!"

if __name__ == '__main__':

? ? pass

很明顯第三方支付接口是無法訪問的变秦,因為接口的地址是我DIY的,為了模擬實際中我們無法使用的第三方支付接口框舔。編寫測試用例如下:

import unittest

from unittest import mock

from payment.PayMent import PayApi

class TestPayApi(unittest.TestCase):

? ? def test_success(self):

? ? ? ? pay = PayApi()

? ? ? ? pay.auth = mock.Mock(return_value={'status_code':'200'})

? ? ? ? status = pay.pay('1000', '12345', '10000')

? ? ? ? self.assertEqual(status, '支付成功')

? ? def test_fail(self):

? ? ? ? pay = PayApi()

? ? ? ? pay.auth = mock.Mock(return_value={'status_code':'500'})

? ? ? ? status = pay.pay('1000', '12345', '10000')

? ? ? ? self.assertEqual(status, '支付失敗')

? ? def test_error(self):

? ? ? ? pay = PayApi()

? ? ? ? pay.auth = mock.Mock(return_value={'status_code':'300'})

? ? ? ? status = pay.pay('1000', '12345', '10000')

? ? ? ? self.assertEqual(status, '未知錯誤')

? ? def test_exception(self):

? ? ? ? pay = PayApi()

? ? ? ? pay.auth = mock.Mock(return_value='200')

? ? ? ? status = pay.pay('1000', '12345', '10000')

? ? ? ? self.assertEqual(status, 'Error, 服務器異常!')

if __name__ == '__main__':

? ? unittest.main()

測試輸出結(jié)果:

....用戶1000支付失敗, 金額不變

用戶1000支付金額10000成功

----------------------------------------------------------------------

Ran 4 tests in 0.001s

OK

Process finished with exit code 0

????從執(zhí)行結(jié)果可以看出蹦玫,即使第三方支付接口無法使用,但是我們自己的支付接口仍然測試通過了刘绣。也許有人會問樱溉,第三方支付都不能用,我們的測試結(jié)果是否是有效的呢纬凤?通常我們在測試一個模塊的時候福贞,我們是可以認為其他模塊的功能是正常的,只針對目標模塊進行測試是沒有任何問題的停士,所以說測試結(jié)果也是正確的挖帘。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市恋技,隨后出現(xiàn)的幾起案子拇舀,更是在濱河造成了極大的恐慌,老刑警劉巖蜻底,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件骄崩,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機要拂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門抠璃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人脱惰,你說我怎么就攤上這事搏嗡。” “怎么了枪芒?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵彻况,是天一觀的道長。 經(jīng)常有香客問我舅踪,道長纽甘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任抽碌,我火速辦了婚禮悍赢,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘货徙。我一直安慰自己左权,他們只是感情好,可當我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布痴颊。 她就那樣靜靜地躺著赏迟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蠢棱。 梳的紋絲不亂的頭發(fā)上锌杀,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天,我揣著相機與錄音泻仙,去河邊找鬼糕再。 笑死,一個胖子當著我的面吹牛玉转,可吹牛的內(nèi)容都是我干的突想。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼究抓,長吁一口氣:“原來是場噩夢啊……” “哼猾担!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起刺下,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤垒探,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后怠李,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體圾叼,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡蛤克,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了夷蚊。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片构挤。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖惕鼓,靈堂內(nèi)的尸體忽然破棺而出筋现,到底是詐尸還是另有隱情,我是刑警寧澤箱歧,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布矾飞,位于F島的核電站,受9級特大地震影響呀邢,放射性物質(zhì)發(fā)生泄漏洒沦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一价淌、第九天 我趴在偏房一處隱蔽的房頂上張望申眼。 院中可真熱鬧,春花似錦蝉衣、人聲如沸括尸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽濒翻。三九已至,卻和暖如春啦膜,著一層夾襖步出監(jiān)牢的瞬間肴焊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工功戚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人似嗤。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓啸臀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親烁落。 傳聞我的和親對象是個殘疾皇子乘粒,可洞房花燭夜當晚...
    茶點故事閱讀 43,724評論 2 351