Mock 技术
- 1)Mock 服务解决问题:解决外部依赖,某些功能依赖,前后端联调等。
- 2)Mock 设计:主要是桩和驱动。桩:被调用模块;驱动:调用的模块
- 3)Mock 目的:模拟数据/模拟对象/模拟实现,进行想要的测试,常见的有:EasyMock,Mockito,WireMock等,主要用于单元测试。
Mock 原则
- 被测试函数里面所对应的方法,如果没实现,就需要 mock
- 无论对象有多少层,必须保证 mock 的对象是断言所调用的对象(同一对象)
mock 对象的主要参数
- return_value,mock 的返回值,在定义mock对象时需指定某个值,创建mock对象后,可以通过该对象调用该属性值。
- side_effect,当实际程序代码已实现,不需要调用 mock 对象时,则会调用实际的程序结果覆盖 return_value 的值
- name:name,mock对象起了个名字。
- spec,可以给mock对象中声明属性,该参数值的格式是列表中的元素是字符串形式,sepc_set的参数为True值的话那么spec参数必须声明。在mock中必须通过spec进行声明属性,无法直接通过mock对象进行添加属性。
mock 对象的常用属性
- called,
- call_count,
- call_args,
- call_args_list
mock 对象的方法(用于断言)
- assert_called_once_with(),当指定方法被多次调用的时候,断言失败。
- assert_called_with(),检查mock方法是否获取了正确的参数,当至少有一个参数有错误的值或者类型时、当参数的个数出错时、当参数的顺序不正确时,断言失败。
- assert_any_call断言用于检查测试执行中的mock对象在测试中是否调用了方法。
- assert_has_calls检查是否按照正确的顺序和正确的参数进行调用的。所以,需要给出一个方法的调用顺序,assert的时候按照这个顺序进行检查。
mock统计方法
- called 跟踪mock对象所做的任意调用的访问器。called只要检测到mock对象被调用,就返回True。
- call_count mock对象被调用次数。call_count检查mock对象被调用了多少次。
- call_args && call_args_list mock对象的初始化参数。call_args_list以列表的形式返回工厂调用时所有的参数。
- method_calls 以列表的形式返回mock对象都调用了哪些方法。
- mock_calls 显示工厂调用和方法调用。
mock管理方法
- acttach_mock acttach_mock将一个mock对象添加到另一个mock对象中。需要注意的是,attach_mock(self, mock, attribute)必须为添加进来的mock对象指定一个属性名。
- configure_mock configure_mock用来更改mock对象的return_value值。
- mock_add_spec mock_add_spec(self, spec, spec_set=False)用来给mock对象添加一个新的属性,新的属性会覆盖掉原来的属性。spec_set指属性可读可写,默认是只读。
- reset_mock reset_mock将mock对象回复到初识状态,避免了重新构造mock对象带来的开销。
python单元测试框架
(1) 单元测试框架:unittest(python自带库),pytest(pyhton地方库)
(2) unittest单元测试框架作用: 管理和组织测试用例
(3) unittest框架格式:
unittest实战1-unittest框架格式使用
# ------ unitTest.py 来讲解整个unittest框架格式使用------
import unittest # 导入unittest库
class unitTest(unittest.TestCase): # (定义一个类,一个testcase的实例就是用例)
@classmethod # 类的方法
def setUpClass(cls) -> None: # 类的开始
print("类的开始")
@classmethod
def tearDownClass(cls) -> None: # 类的结束
print("类的结束")
def setUp(self) -> None: # 方法开始
print("方法开始")
def tearDown(self) -> None: # 方法的结束
print("方法结束")
def test01(self): # 测试用例
print("用例111")
def test03(self): # 测试用例
print("用例333")
def test02(self): # 测试用例
print("用例222")
def testa(self): # 测试用例
print("用例aaa")
def testA(self): # 测试用例
print("用例AAA")
def testb(self): # 测试用例
print("用例bbb")
def dl(self):
print("独立")
if __name__ == '__main__':
unittest.main() # nittest单元测试框架的入口, main调用所有用例
============================= test session starts =============================
collecting ... collected 6 items
unitTest.py::unitTest::test01 类的开始
PASSED [ 16%]方法开始
用例111
方法结束
unitTest.py::unitTest::test02 PASSED [ 33%]方法开始
用例222
方法结束
unitTest.py::unitTest::test03 PASSED [ 50%]方法开始
用例333
方法结束
unitTest.py::unitTest::testA PASSED [ 66%]方法开始
用例AAA
方法结束
unitTest.py::unitTest::testa PASSED [ 83%]方法开始
用例aaa
方法结束
unitTest.py::unitTest::testb PASSED [100%]方法开始
用例bbb
方法结束
类的结束
======================== 6 passed, 1 warning in 0.06s =========================
Process finished with exit code 0
备注:
(1)类的开始和类的结束,在框架中只运行一次
(2)方法的开始,方法的结束每一个用例都要执行一次
(3)运行顺序是按ascii码表排序:0-9A-Za-z
(4)用例必须以test开头,如果没有test就不运行
unittest实战2-Mock该函数方法未实现
# ------ Cal.py (该函数方法未实现)------
class Cal(object):
# 加法
def add(self, a, b):
pass
# 减法
def minus(self, a, b):
pass
#---------- CalTest.py ---------
import unittest
from unittest import mock
from Mock_Tech.SourceDir.Cal import Cal
class CalTest(unittest.TestCase):
def setUp(self) -> None: # 方法开始
self.cal = Cal() # 创建cal的实例
# 假设 cal.minus() 方法输入参数 4,6, 输出 0
def test_minus(self):
# 实例化Mock类得到一个mock对象,并设置mock对象的行为
get_mock = mock.Mock(return_value=0)
# cal.minus为要mock的对象,可以是一个类/函数/类实例
# 此时 self.cal.minus 表示的是对象,如果是 self.cal.minus() 则表示的是 方法
self.cal.minus = get_mock
self.assertEqual(self.cal.minus(4,6), 0)
if __name__ == '__main__':
unittest.main() # unittest单元测试框架的入口, main调用所有用例
unittest实战3-Mock该函数方法已实现
#------- Cal1.py --------
class Cal(object):
def add(self, a, b):
pass
# 未实现的方法
def minus(self, a, b):
pass
# 已经实现了的方法
def minus1(self, a, b):
return a - b + 2
#-------- CalTest1.py ---------
import unittest
from unittest import mock
from Mock_Tech.SourceDir.Cal import Cal
class CalTest(unittest.TestCase):
def setUp(self) -> None:
self.cal = Cal()
# 假设 cal.minus() 方法输入参数 4,6, 输出 0
def test_minus(self):
get_mock = mock.Mock(return_value=0)
# 此时 self.cal.minus 表示的是对象,如果是 self.cal.minus() 则表示的是 方法
self.cal.minus = get_mock
self.assertEqual(self.cal.minus(4, 6), 0)
# 测试已经实现了的 minus1 方法
# side_effect 参数,将 实际产生的值覆盖 return_value
def test_minus1(self):
get_mock = mock.Mock(return_value=0, side_effect=self.cal.minus1)
self.cal.minus1 = get_mock
self.assertEqual(self.cal.minus1(4, 13), 0) # 此时输入的参数,得到的结果就断言失败
self.assertEqual(self.cal.minus1(4, 13), -7) # 该结果正确
if __name__ == '__main__':
unittest.main()
unittest实战4-Mock接口请求
#------ Login_Interface.py (该接口服务目前无法启动)--------
import requests
def login_interface(data):
get_response = requests.get("http://127.0.0.1:30060/loginAction", params=data)
return get_response.json()
#-------- LoginTest.py (需要测试接口功能)---------
# @Description : mock 接口对象
import unittest
import requests
from unittest import mock
class LoginTest(unittest.TestCase):
def setUp(self) -> None:
pass
def test_login_badCase1(self):
# mock 接口对象,但是 login_interface 没有调用 Login_Interface 的 login_interface 方法
data = {"username": "admin", "password": "123456"}
expect = {"errorcode": "0", "message": "登录成功"}
get_mock = mock.Mock(return_value=expect)
# login_interface 是一个对象,所以该处可以 mock 成功
login_interface = get_mock
print(login_interface(data)) # {'errorcode': '0', 'message': '登录成功'}
def test_login_badCase2(self):
# 该例子中, self.get_response 是一个变量,实际运行中没有 mock 成功
data = {"username": "admin", "password": "123456"}
self.get_response = requests.get("http://127.0.0.1:30060/loginAction", params=data).json()
get_mock = mock.Mock(return_value=data)
# ConnectionRefusedError: [WinError 10061] 由于目标计算机积极拒绝,无法连接。此时 mock 对象是赋值给一个变量值,不是对象
self.get_response = get_mock
print(self.get_response)
def test_login_badCase3(self):
# mock 传个 对象
data = {"username": "admin", "password": "123456"}
# 该例子中, get_response 实际上没有使用到
get_response = lambda: requests.get("http://127.0.0.1:30060/loginAction", params=data).json()
get_mock = mock.Mock(return_value=data)
get_response = get_mock
print(get_response()) # {'username': 'admin', 'password': '123456'}
def test_login_realWant(self):
# mock 传个 对象。区别于 case3, 这里的 get_response 加上了 "self." 作为当期对象
data = {"username": "admin", "password": "123456"}
# lambda 生成对象,所以该列子中可以 mock 成功。区别于 case3,加上 self. 之后,self.get_response 对象有实际被使用
self.get_response = lambda: requests.get("http://127.0.0.1:30060/loginAction", params=data).json()
get_mock = mock.Mock(return_value=data)
self.get_response = get_mock
print(self.get_response()) # {'username': 'admin', 'password': '123456'}
if __name__ == '__main__':
unittest.main()
unittest实战5
#------- Mobile.py (该功能已实现) -----
from Mock_Tech.SourceDir.Cal import Cal
class Mobile(object):
def __init__(self):
self.cal = Cal()
def get_add_int(self, a, b):
return int(self.cal.add(a, b))
def get_minus_int(self, a, b):
return int(self.cal.minus(a, b))
# 不同于 cal 对象,此处传入 add 对象
def get_add_int_1(self, add, a, b):
return int(add(a, b))
#------- MobileTest.py (需要测试 Mobile.py 的函数功能)----------
# @Description : 1. mock 实现; 2. 设计原则:源代码和测试代码分开,即可以分开成 SourceDir 和 TestDir
import unittest
from unittest import mock
from Mock_Tech.SourceDir.Mobile import Mobile
from Mock_Tech.SourceDir.Cal import Cal
class MobileTest(unittest.TestCase):
def setUp(self) -> None:
self.mobile = Mobile()
self.cal = Cal()
def test_get_add_int_badCase1(self):
# 创建 mock 对象;模拟 Cal 中 add() 返回值
mock_add = mock.Mock(return_value=6.0)
# 模拟 Mobile get_add_int 对象。(如果模拟 get_add_int 方法,是假设 get_add_int 方法还未实现。实际是 get_add_int()有实现,但其内部调用 cal.add() 未实现)
self.mobile.get_add_int = mock_add
self.assertEqual(6, self.mobile.get_add_int(1.2, 5.2))
def test_get_add_int_badCase2(self):
mock_add = mock.Mock(return_value=6.0)
# 将 mock 对象赋值给 python 对象。模拟 add 对象,但是没有测试 mobile 的方法 get_add_nit
self.cal.add = mock_add
self.assertEqual(6.0, self.cal.add(2.0, 3.0))
def test_get_add_int_realWant(self):
mock_add = mock.Mock(return_value=6.0)
# => 实际上 mock 的应该是 mobile cal 的 add 对象。这里指的是 SourceDir 里的 cal.add 对象
self.mobile.cal.add = mock_add
# => 并且测试 mobile 的方法 get_add_int
self.assertEqual(6, self.mobile.get_add_int(2.0, 3.0))
def test_get_add_int_1(self):
# 针对 Mobile 的 get_add_int_1 的形式,使用如下的 mock
# 将 add 以对象形式传入到 get_add_int_1 的方法中。此时,有2种实现:1)mock cal.add 或 2)mock mobile.cal.add
mock_add = mock.Mock(return_value=6.0)
# 2.1) 这个 cal.add 是本测试类的 cal.add 对。该 mock 一层对象(cal.add)
self.cal.add = mock_add
self.assertEqual(6, self.mobile.get_add_int_1(self.cal.add, 2.0, 3.0))
# 2.2) 这个 cal.add 是 SourceDir 的 cal.add 对象。这个是 mock 对象(mobile)里面的对象(cal.add),即 二层对象
self.mobile.cal.add = mock_add
self.assertEqual(6, self.mobile.get_add_int_1(self.mobile.cal.add, 2.0, 3.0))
if __name__ == '__main__':
unittest.main()
unittest实战6- 实现一个测试用例涉及多个对象
#----------- Mobile1.py ------------
from Mock_Tech.SourceDir.Cal import Cal
class Mobile1(object):
def __init__(self):
self.cal = Cal()
def get_add_int(self, a, b):
return int(self.cal.add(a, b))
def get_minus_int(self, a, b):
return int(self.cal.minus(a, b))
# 不同于 cal 对象,此处传入 add 对象
def get_add_int_1(self, add, a, b):
return int(add(a, b))
def get_add_minus_int(self, a, b):
return int(self.cal.add(a, b) - self.cal.minus(a, b))
#--------- MobileTest2.py ---------
import unittest
from unittest import mock
from Mock_Tech.SourceDir.Mobile1 import Mobile1
from Mock_Tech.SourceDir.Cal1 import Cal1
class MobileTest(unittest.TestCase):
def setUp(self) -> None:
self.mobile = Mobile1()
self.cal1 = Cal1()
def test_get_add_minus_int(self):
# 实现一个测试用例涉及多个对象
mock_add = mock.Mock(return_value=10)
mock_minus = mock.Mock(return_value=0)
self.mobile.cal.add = mock_add # 绝对不能 self.cal.add = mock_add,需要 二层mock
self.mobile.cal.minus = mock_minus
self.assertEqual(self.mobile.get_add_minus_int(4, 6), 10)
if __name__ == '__main__':
unittest.main()
unittest实战7-测试 mock + parameterized
# ----- MobileTest3.py --------
# -*- coding: utf-8 -*-
# @Time : 2023/1/8 15:02
# @Author : Bruce He
# @File : MobileTest3.py
# @Project Name: Lesson_FullStack_TD
# @Description : 测试 mock + parameterized
import unittest
from unittest import mock
from parameterized import parameterized
from Mock_Tech.SourceDir.Mobile import Mobile
from Mock_Tech.SourceDir.Cal import Cal
class MobileTest(unittest.TestCase):
def setUp(self) -> None:
self.mobile = Mobile()
self.cal = Cal()
@parameterized.expand([(16, 4, 9, 7, 12), (10, 0, 4, 6, 10)])
def test_parameterized_get_add_minus_int(self, mock1, mock2, input1, input2, expect):
# 实现一个测试用例涉及多个对象
mock_add = mock.Mock(return_value=mock1)
mock_minus = mock.Mock(return_value=mock2)
self.mobile.cal.add = mock_add # 此处的 self.mobile.cal.add 是 mock 二层对象
self.mobile.cal.minus = mock_minus
self.assertEqual(self.mobile.get_add_minus_int(input1, input2), expect)
if __name__ == '__main__':
unittest.main()