0
点赞
收藏
分享

微信扫一扫

Python 全栈测试开发 Chapter11 Mock测试

船长_Kevin 2024-03-15 阅读 13

    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()

    举报

    相关推荐

    0 条评论