一、mock簡(jiǎn)介
mock原是python的第三方庫(kù)
python3以后mock模塊已經(jīng)整合到了unittest測(cè)試框架中,不用再單獨(dú)安裝
Mock這個(gè)詞在英語(yǔ)中有模擬的意思谚殊,因此我們可以猜測(cè)出這個(gè)庫(kù)的主要功能是模擬一些東西
準(zhǔn)確的說(shuō),Mock是Python中一個(gè)用于支持單元測(cè)試的庫(kù)浑测,它的主要功能是使用mock對(duì)象替代掉指定的Python對(duì)象档礁,以達(dá)到模擬對(duì)象的行為
既然mock已經(jīng)被整合到了unittest單元測(cè)試框架中,可想而知mock的目的就是為了讓我們更好的進(jìn)行測(cè)試
二赚抡、mock作用
1. 解決依賴(lài)問(wèn)題:當(dāng)我們測(cè)試一個(gè)接口或者功能模塊的時(shí)候爬坑,如果這個(gè)接口或者功能模塊依賴(lài)其他接口或其他模塊,那么如果所依賴(lài)的接口或功能模塊未開(kāi)發(fā)完畢涂臣,那么我們就可以
使用mock模擬被依賴(lài)接口盾计,完成目標(biāo)接口的測(cè)試
2. 單元測(cè)試:如果某個(gè)功能未開(kāi)發(fā)完成,我們又要進(jìn)行測(cè)試用例的代碼編寫(xiě)赁遗,我們也可以先模擬這個(gè)功能進(jìn)行測(cè)試
3. 模擬復(fù)雜業(yè)務(wù)的接口:實(shí)際工作中如果我們?cè)跍y(cè)試一個(gè)接口功能時(shí)署辉,如果這個(gè)接口依賴(lài)一個(gè)非常復(fù)雜的接口業(yè)務(wù),那么我們完全可以使用mock來(lái)模擬這個(gè)復(fù)雜的業(yè)務(wù)接口岩四,其實(shí)
這個(gè)和解決接口依賴(lài)是一樣的原理
4.前后端聯(lián)調(diào):如果你是一個(gè)前端頁(yè)面開(kāi)發(fā)哭尝,現(xiàn)在需要開(kāi)發(fā)一個(gè)功能:根據(jù)后臺(tái)返回的狀態(tài)展示不同的頁(yè)面,那么你就需要調(diào)用后臺(tái)的接口剖煌,但是后臺(tái)接口還未開(kāi)發(fā)完成材鹦,是不是你
就停止這部分工作呢?答案是否定的耕姊,你完全可以借助mock來(lái)模擬后臺(tái)這個(gè)接口返回你想要的數(shù)據(jù)
三桶唐、mock安裝
python 3 的mock模塊已經(jīng)被整合到了unittest框架中,所以你使用的時(shí)候只需要在文件開(kāi)頭from unittest import mock 導(dǎo)入即可
如果你使用的是python2 那么你需要執(zhí)行pip install mock安裝后再 import mock即可
四茉兰、mock實(shí)例
一個(gè)未開(kāi)發(fā)完成的功能如何測(cè)試莽红?
假如們現(xiàn)在有一個(gè)實(shí)現(xiàn)兩個(gè)數(shù)相加的功能需要編寫(xiě)測(cè)試用例,但是由于開(kāi)發(fā)進(jìn)度緩慢邦邦,只搭兩個(gè)簡(jiǎn)單的框架,并沒(méi)有內(nèi)部實(shí)現(xiàn)
"""
------------------------------------
@Time : 2020/7/21 14:09
@Auth : 梵音
@File : ClassFunc.py
@IDE? : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ? : 829792258@qq.com
@GROUP: 829792258
------------------------------------
"""
import unittest
from unittest import mock
class SubClass(object):
? ? def add(self, a, b):
? ? ? ? """兩個(gè)數(shù)相加"""
? ? ? ? pass
class TestSub(unittest.TestCase):
"""測(cè)試兩個(gè)數(shù)相加用例"""
? ? def test_sub(self):
? ? ? ? sub = SubClass()? # 初始化被測(cè)函數(shù)類(lèi)實(shí)例
? ? ? ? sub.add = mock.Mock(return_value=10)? # mock add方法 返回10
? ? ? ? result = sub.add(5, 5)? # 調(diào)用被測(cè)函數(shù)
? ? ? ? self.assertEqual(result, 10)? # 斷言實(shí)際結(jié)果和預(yù)期結(jié)果
if __name__ == '__main__':
? ? unittest.main()
測(cè)試結(jié)果
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Process finished with exit code
測(cè)試結(jié)果顯示醉蚁,測(cè)試用例執(zhí)行已經(jīng)通過(guò)
實(shí)際上mock模擬add方法的原理是 使用相同的對(duì)象方法接收mock的對(duì)象(使用sub.add接收)燃辖,那么當(dāng)mock對(duì)象被調(diào)用時(shí)(sub.add())就會(huì)返回return_value參數(shù)對(duì)應(yīng)的數(shù)據(jù)
你可以做一個(gè)實(shí)驗(yàn),把用例中的add改成別的名字也一樣可以測(cè)試通過(guò)
我們用例編寫(xiě)完了网棍,而且開(kāi)發(fā)既然也把功能開(kāi)發(fā)完了黔龟,既然真實(shí)的功能已經(jīng)可以測(cè)試了,那么我們?cè)趺丛谏厦嬗美幕A(chǔ)上直接測(cè)試真實(shí)功能呢?
完整的功能如何測(cè)試氏身?
我們把用例的代碼稍做修改
"""
------------------------------------
@Time : 2020/7/21 14:09
@Auth : 梵音
@File : ClassFunc.py
@IDE? : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ? : 829792258@qq.com
@GROUP: 829792258
------------------------------------
"""
import unittest
from unittest import mock
class SubClass(object):
? ? def add(self, a, b):
? ? ? ? """兩個(gè)數(shù)相加"""
? ? ? ? return a + b
class TestSub(unittest.TestCase):
? ? """測(cè)試兩個(gè)數(shù)相加"""
? ? def test_sub(self):
? ? ? ? sub = SubClass()? # 初始化被測(cè)函數(shù)類(lèi)實(shí)例
? ? ? ? sub.add = mock.Mock(return_value=10, side_effect=sub.add)? # 傳遞side_effect關(guān)鍵字參數(shù), 會(huì)覆蓋return_value參數(shù)值, 使用真實(shí)的add方法測(cè)試
? ? ? ? result = sub.add(5, 11)? # 真正的調(diào)用被測(cè)函數(shù)
? ? ? ? self.assertEqual(result, 16)? # 斷言實(shí)際結(jié)果和預(yù)期結(jié)果
if __name__ == '__main__':
? ? unittest.main()
side_effect參數(shù)
代碼中我們給Mock方法添加了另一個(gè)關(guān)鍵字參數(shù)side_effect = sub.add巍棱, 這個(gè)參數(shù)和return_value 正好相反,當(dāng)傳遞這個(gè)參數(shù)的時(shí)候return_value 參數(shù)就會(huì)失效
而side_effect生效蛋欣,這里我給的參數(shù)值是sub.add 相當(dāng)于add方法的地址航徙,那么當(dāng)調(diào)用add方法時(shí)就會(huì)真實(shí)的使用add方法,也就達(dá)到了我們測(cè)試實(shí)際的add 方法陷虎。
你也可以理解為當(dāng)傳遞了side_effect參數(shù)且值為被測(cè)方法地址時(shí)到踏,mock就不會(huì)起作用
side_effect接收的是一個(gè)可迭代序列,當(dāng)傳遞多個(gè)值時(shí)尚猿,那么每次調(diào)用mock時(shí)會(huì)返回不同的值
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
當(dāng)所有值被取完后就會(huì)報(bào)錯(cuò)(這個(gè)地方有點(diǎn)類(lèi)似生成器的原理)
存在依賴(lài)關(guān)系的功能如何測(cè)試窝稿?
假設(shè)有這樣一個(gè)場(chǎng)景:我們要測(cè)試一個(gè)支付接口但是這個(gè)支付接口又依賴(lài)一個(gè)第三方支付接口,那么第三方支付接口我們暫時(shí)沒(méi)有權(quán)限使用凿掂,那么我們?cè)撊绾螠y(cè)試我們自己這個(gè)接口呢伴榔?
看下面的實(shí)例
假設(shè)第三方接口和我們自己的支付接口如下
"""
------------------------------------
@Time :2020/7/21 15:09
@Auth : 梵音
@File : PayMent.py
@IDE? : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ? : 829792258@qq.com
@GROUP: 829792258
------------------------------------
"""
import requests
class PayApi(object):
? ? @staticmethod
? ? def auth(card, amount):
? ? ? ? """
? ? ? ? 第三方支付接口
? ? ? ? :param card: 卡號(hào)
? ? ? ? :param amount: 支付金額
? ? ? ? :return:
? ? ? ? """
? ? ? ? pay_url = "http://www.zhifubao.com"? # 第三方支付接口地址
? ? ? ? data = {"card": card, "amount": amount}
? ? ? ? response = requests.post(pay_url, data=data)? # 請(qǐng)求第三方支付接口
? ? ? ? return response? # 返回狀態(tài)碼
? ? def pay(self, user_id, card, amount):
? ? ? ? """
? ? ? ? 我們自己的支付接口
? ? ? ? :param user_id: 用戶(hù)id
? ? ? ? :param card: 卡號(hào)
? ? ? ? :param amount: 支付金額
? ? ? ? :return:
? ? ? ? """
# 調(diào)用第三方支付接口
? ? ? ? response = self.auth(card, amount)
? ? ? ? try:
? ? ? ? ? ? if response['status_code'] == '200':
? ? ? ? ? ? ? ? print('用戶(hù){}支付金額{}成功'.format(user_id, amount))
? ? ? ? ? ? ? ? return '支付成功'
? ? ? ? ? ? elif response['status_code'] == '500':
? ? ? ? ? ? ? ? print('用戶(hù){}支付失敗, 金額不變'.format(user_id))
? ? ? ? ? ? ? ? return '支付失敗'
? ? ? ? ? ? else:
? ? ? ? ? ? ? ? return '未知錯(cuò)誤'
? ? ? ? except Exception:
? ? ? ? ? ? return "Error, 服務(wù)器異常!"
if __name__ == '__main__':
? ? pass
很明顯第三方支付接口是無(wú)法訪問(wèn)的,因?yàn)榻涌诘牡刂肥俏褼IY的庄萎,為了模擬實(shí)際中我們無(wú)法使用的第三方支付接口
編寫(xiě)測(cè)試用例
"""
------------------------------------
@Time : 2020/7/21 15:22
@Auth : 梵音
@File : testpay.py
@IDE? : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ? : 829792258@qq.com
@GROUP: 829792258
------------------------------------
"""
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, '未知錯(cuò)誤')
? ? def test_exception(self):
? ? ? ? pay = PayApi()
? ? ? ? pay.auth = mock.Mock(return_value='200')
? ? ? ? status = pay.pay('1000', '12345', '10000')
? ? ? ? self.assertEqual(status, 'Error, 服務(wù)器異常!')
if __name__ == '__main__':
? ? unittest.main()
測(cè)試輸出結(jié)果
....用戶(hù)1000支付失敗, 金額不變
用戶(hù)1000支付金額10000成功
----------------------------------------------------------------------
Ran 4 tests in 0.001s
OK
Process finished with exit code 0
從執(zhí)行結(jié)果可以看出踪少,即使第三方支付接口無(wú)法使用,但是我們自己的支付接口仍然測(cè)試通過(guò)了
也許有人會(huì)問(wèn)惨恭,第三方支付都不能用秉馏,我們的測(cè)試結(jié)果是否是有效的呢?
通常我們?cè)跍y(cè)試一個(gè)模塊的時(shí)候脱羡,我們是可以認(rèn)為其他模塊的功能是正常的萝究,只針對(duì)目標(biāo)模塊進(jìn)行測(cè)試是沒(méi)有任何問(wèn)題的,所以說(shuō)測(cè)試結(jié)果也是正確的
其實(shí)上述代碼還可以使用另一種方式來(lái)寫(xiě)
mock對(duì)象的方法
"""
------------------------------------
@Time : 2020/7/21 15:22
@Auth :梵音
@File : testpay.py
@IDE? : PyCharm
@Motto: Real warriors,dare to face the bleak warning,dare to face the incisive error!
@QQ? : 829792258@qq.com
@GROUP:829792258
------------------------------------
"""
import unittest
from unittest import mock
from unittest.mock import patch
from payment.PayMent import PayApi
class TestPayApi(unittest.TestCase):
? ? def setUp(self):
? ? ? ? self.pay = PayApi()
? ? @patch.object(PayApi, 'auth')
? ? def test_success(self, mock_auth):
? ? ? ? mock_auth.return_value = {'status_code':'200'}
? ? ? ? status = self.pay.pay('1000', '12345', '10000')
? ? ? ? self.assertEqual(status, '支付成功')
? ? @patch.object(PayApi, 'auth')
? ? def test_fail(self, mock_auth):
? ? ? ? mock_auth.return_value={'status_code':'500'}
? ? ? ? status = self.pay.pay('1000', '12345', '10000')
? ? ? ? self.assertEqual(status, '支付失敗')
? ? @patch.object(PayApi, 'auth')
? ? def test_error(self, mock_auth):
? ? ? ? mock_auth.return_value={'status_code':'300'}
? ? ? ? status = self.pay.pay('1000', '12345', '10000')
? ? ? ? self.assertEqual(status, '未知錯(cuò)誤')
? ? @patch.object(PayApi, 'auth')
? ? def test_exception(self, mock_auth):
? ? ? ? mock_auth.return_value='200'
? ? ? ? status = self.pay.pay('1000', '12345', '10000')
? ? ? ? self.assertEqual(status, 'Error, 服務(wù)器異常!')
if __name__ == '__main__':
? ? unittest.main()
還有mock一個(gè)普通函數(shù)锉罐,mock多個(gè)方法等帆竹,這里先不贅述,寫(xiě)法和上面實(shí)例差不多創(chuàng)建了一個(gè)測(cè)試交流群脓规,如果對(duì)軟件測(cè)試栽连、接口測(cè)試、自動(dòng)化測(cè)試侨舆、面試經(jīng)驗(yàn)交流感興趣可以加測(cè)試交流群:829792258秒紧,還會(huì)有同行一起技術(shù)交流