模拟数据测试系列之mock库简介

1,395 阅读4分钟

1.模拟测试简介

在测试过程中,数据的流转往往和系统的复杂度有关,功能越复杂的系统,数据流转的过程越繁琐。有时候会经过数十个子系统,其上下游业务相互依赖,使得测试过程十分复杂、漫长。比如电商网站进行购物,需要经过选择商品、加入购物车、支付订单、领取积分、退款环节,环环相扣,缺一不可。支付订单的前提是已经生成订单,生成订单的前提是已经选择好商品

针对这类场景,在对某一中间环节进行测试时,使用模拟数据的方式来替代上游流程将会更加高效。

2.模拟测试的使用场景

模拟测试主要用于如下的场景:

相互依赖的函数调用过程

相互依赖的上下游服务之间的测试,通过模拟数据来源,达到精准测试特定环境或特定功能的作用。

在测试环境不够稳定或还处于开发阶段时,可以通过模拟接口返回相关结果,达到加快测试进度的作用。服务、接口、一些特定环境都可以进行模拟。

3.Mock对象简介

3.1 Mock对象定义

class Mock(CallableMixin, NonCallableMock):
    pass
class CallableMixin(Base):
​
    def __init__(self, spec=None, side_effect=None, return_value=DEFAULT,
                 wraps=None, name=None, spec_set=None, parent=None,
                 _spec_state=None, _new_name='', _new_parent=None, **kwargs):
        pass
class NonCallableMock(Base):
​
    def __init__(
            self, spec=None, wraps=None, name=None, spec_set=None,
            parent=None, _spec_state=None, _new_name='', _new_parent=None,
            _spec_as_instance=False, _eat_self=None, unsafe=False, **kwargs
        ):
        pass

这里对几个参数进行具体的解释

spec:可以在初始化的时候设置的Mock对象属性,可以是字符串、列表或者对象。当然,你也可以不传

wraps:如果设置了该参数,那么会把调用结果传递给Mock对象,对模拟属性的访问将返回包装后的对象属性。如果试图访问一个不存在的属性,将引发一个属性错误。

spec_set:该参数算是严格的spec,只能传递set类型的参数。

return_value:设置预期的返回值给Mock对象,后续在测试逻辑判断中可以使用这个参数。

3.2 代码实例

3.2.1 计算两数之和

from unittest import mock
import unittest
​
​
# 计算两数之和
class SimpleCalculator(object):
​
    def sum(self, num1: int, num2: int) -> int:
        return num1 + num2
​
​
# 测试用例类
class SumTest(unittest.TestCase):
​
    def test(self):
        s = SimpleCalculator()
        num1 = 10
        num2 = 30
        sum_result = mock.Mock(return_value=40)
        s.sum = sum_result
        self.assertEqual(s.sum(num1, num2), 40)
​
​
if __name__ == '__main__':
    sum_test = SumTest()
    sum_test.test()

result:

Testing started at 12:00 ...
D:\software\python\python.exe "D:\software\PyCharm 2019.3.4\plugins\python\helpers\pycharm_jb_unittest_runner.py" --path D:/python_record/mock_01.py
​
​
Ran 1 test in 0.002s
​
OK
Launching unittests with arguments python -m unittest D:/python_record/mock_01.py in D:\python_record

3.2.2 get请求状态码测试

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2021/8/8 11:19
# @Author  : ywb
# @Site    : 
# @File    : mock_02
# @Software: PyCharm
​
​
import requests
from unittest import mock
import unittest
​
​
class Client(object):
​
    def send_request(self, url: str) -> str:
        """
        发送get请求
        :param url: 请求的url地址
        :return: 响应状态码
        """
        response = requests.get(url)
        return response.status_code
​
    def visit_baidu(self) -> str:
        """
        访问百度
        :return: 响应状态码
        """
        return self.send_request('https://www.baidu.com')
​
​
class TestClient(unittest.TestCase):
​
    def test_success_request(self):
        """
        测试请求成功时的情形
        """
        success_send = mock.Mock(return_value='200')
        client = Client()
        client.send_request = success_send
        self.assertEqual(client.visit_baidu(), '200')
​
    def test_fail_request(self):
        """
        测试请求失败时的情形
        """
        success_send = mock.Mock(return_value='403')
        client = Client()
        client.send_request = success_send
        self.assertEqual(client.visit_baidu(), '403')
​
​
if __name__ == '__main__':
    unittest.main()

result:

Testing started at 12:01 ...
D:\software\python\python.exe "D:\software\PyCharm 2019.3.4\plugins\python\helpers\pycharm_jb_unittest_runner.py" --path D:/python_record/mock_02.py
Launching unittests with arguments python -m unittest D:/python_record/mock_02.py in D:\python_record
​
​
​
Ran 2 tests in 0.002s
​
OKProcess finished with exit code 0

这里对上述代码解释一下。这里的逻辑是这样的,我们准备测试的对象是send_request()方法,而该方法目前还没有开发完成。但是我们知道该方法返回的是get请求的响应码。根据这一信息我们进行模拟。

首先在测试类TestClient中,让其继承unittest.TestCase,是便于用例组织和管理以及断言判断。针对上述的测试对象send_request()方法,TestClient中书写了两个测试用例,test_success_request()方法是测试请求成功时的响应码是不是200,test_fail_request()方法是为了测试请求失败时的响应码是否是403。

具体以test_success_request()方法为例进行说明,

success_send = mock.Mock(return_value='200')

这一句是设置了一个mock对象,其属性return_value的值是‘200’

client.send_request = success_send

这一句是用设置的mock对象来替换我们要测试的真实函数send_request 。换句话说,现在由于send_request ()方法未开发完成。我们现在用mock对象来代替send_request()方法。调用mock对象和send_request()方法时等价的。所以这时候我们虽然看着调用了send_request()方法,但实际上其逻辑行为完全是从mock对象中进行的,不会再进入send_request()方法内部。所以我们最终获取的返回值其实是mock对象之前早已经设置好的返回值。

4 Mock对象的使用步骤总结

(1)找到要替换的函数

(2)实例化Mock对象,并设置它的属性或者行为。在文章中的两个例子都是设置了其返回值

(3) 替换想要替换的函数或者对象

(4)使用unittest进行断言测试