mock测试

1. 简述

mock测试就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法。

典型的应用场景:

  1. 当某个单元测试依赖另外一个函数,而这个函数还未开发完成,那么可以使用这个函数的mock对象来完成测试。
  2. 当某个接口测试依赖另一个接口,而这个接口未开发完成,或不方便调用(例如第三方的支付接口),那么可以使用mock服务模拟这个依赖接口来完成。

2. unittest.mock

from unittest.mock import create_autospec
# create_autospec:校验参数个数,再返回固定值;create_autospec方法替代Mock方法。
# 需要被mock的函数
def some_function(a, b, c):
    """
    返回True或者False
    """
    pass
mock_function = create_autospec(some_function, return_value=True)
mock_function(1,2,3)
True

3. 充值接口的mock测试

mock类解读

class Mock(spec=None,side_effect=None,return_value=DEFFAULT,name=None)

  • secp:定义mock对象的属性值,可以是列表,字符串,甚至一个对象或者实例
  • side_effect:可以用来抛出异常或者动态改变返回值,它必须是一个iterator(列表),它会覆盖return_value
  • return_value:定义mock方法的返回值,它可以是一个值,可以是一个对象(如果存在side_effect参数那这个就没有用,也就是不能同时用)
  • name:作为mock对象的一个标识,在print时可以看到

本文举例讲解unittest中的mock

from unittest import mock
from pprint import pprint

pprint([i for i in dir(mock.Mock) if not ((i.startswith("_") or i.startswith("__")))])

输出

['assert_any_call',
 'assert_called',
 'assert_called_once',
 'assert_called_once_with',
 'assert_called_with',
 'assert_has_calls',
 'assert_not_called',
 'attach_mock',
 'call_args',
 'call_args_list',
 'call_count',
 'called',
 'configure_mock',
 'mock_add_spec',
 'mock_calls',
 'reset_mock',
 'return_value',
 'side_effect']

Process finished with exit code 0

  • 从上面代码种可以得到Mock类有哪些方法
  • 在日常工作中只需要掌握 return_valueside_effect 两个参数

在日常工作中,往往需要对第三方接口,或者开发还未开发的接口,或者有些需要扣费的接口,不让我们调的接口等等。
在接口测试当中,我们又需要调这些接口怎么办?

unittest当中的Mock由此而生为我们解决这等问题。不让调的接口我们用假数据进行Mock掉。只要值符合接口文档即可。

  • 新建一个unittest_mock文件夹
  • 在此目录创建一个 payment.pytest_pay.py
  • 模拟用户支付接口调用第三方支付宝接口
    第三方支付宝接口,我们往往不能掉,因为需要花钱啊,你难道花钱去测试?

payment.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2021/12/16 21:08
# @Author  : shisuiyi
# @File    : payment.py.py
# @Software: win10 Tensorflow1.13.1 python3.9
import requests


class Payment:
    """
    定义第三方支付类
    """
    def auth(self, card_num, amount):
        """
        请求第三方外部支付接口的方法, 返回响应状态码
        :param card_num: 卡号
        :param amount: 金额
        :return: 返回状态码, 200 代表支付成功, 500 代表支付异常, 失败
        """
        url = "http://第三方支付url.payment"
        data = {"card_num": card_num, "amount": amount} # 请求参数
        self.res = requests.post(url, data=data)
        return self.res.status_code  # 返回的状态码

    def pay(self, user_id, card_num, amount):
        """
        支付方法
        :param user_id: 用户ID
        :param card_num: 卡号
        :param amount: 支付金额
        :return:
        """
        try:
            status_code = self.auth(card_num, amount)
        except TimeoutError:
            status_code = self.auth(card_num, amount)   # 如果支付超时, 再请求一次

        if status_code == 200:
            print("[{}]支付[{}]成功!!! 进行扣款并登记支付记录".format(user_id, amount))
            return "success"
        elif status_code == 500:
            print("[{}]支付[{}]失败!!! 不进行扣款".format(user_id, amount))
            return "Fail"

  • 从上面可以看出 auth是第三方接口,我们如何mock掉,怎么测试呢?

test_pay.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2021/12/16 21:09
# @Author  : shisuiyi
# @File    : test_pay.py.py
# @Software: win10 Tensorflow1.13.1 python3.9
import unittest
from unittest import mock

from mockTest.payment import Payment


class PaymentTest(unittest.TestCase):
    """
    测试支付接口
    """
    def setUp(self):
        self.payment = Payment()

    def test_1_success(self):
        """
        测试支付成功
        :return:
        """
        self.payment.auth = mock.Mock(return_value=200)
        res = self.payment.pay(user_id=1001, card_num=12345678, amount=5000000)
        self.assertEqual('success', res)

    def test_2_fail(self):
        """
        测试支付失败
        :return:
        """
        self.payment.auth = mock.Mock(return_value=500)
        res = self.payment.pay(user_id=1001, card_num=12345678, amount=5000000)
        self.assertEqual('Fail', res)

    def test_3_retry_success(self):
        """
        测试调用第三方接口超时之后, 再次支付成功
        :return:
        """
        self.payment.auth = mock.Mock(side_effect=[TimeoutError, 200])
        res = self.payment.pay(user_id=1001, card_num=12345678, amount=5000000)
        self.assertEqual('success', res)

    def test_4_retry_fail(self):
        """
        测试调用第三方接口超时之后, 再次支付失败
        :return:
        """
        # side_effect 第一次会得到列表第一个值抛出TimeoutError的异常,抛出异常payment.py里会再次调用第
        # 三方支付接口auth,接着将列表第2个值 500返回给auth,pay函数里面代码判断 500 为失败 Fail.
        # side_effect 参数可以等于另外一个函数 函数的返回值,作为Mock函数的返回值,例子这里就不举了。
        self.payment.auth = mock.Mock(side_effect=[TimeoutError, 500])
        res = self.payment.pay(user_id=1001, card_num=12345678, amount=5000000)
        self.assertEqual('Fail', res)


if __name__ == '__main__':
    unittest.main()

我们需要测的场景,一般有支付成功, 支付失败。支付超时,回调之后支付成功。支付超时,回调支付失败。其他网络异常支付成功未扣款这里暂时不讨论。先弄懂mock怎么去干这些事的。

self.payment.auth = mock.Mock(return_value=200) return_value的值直接作为要mock掉的函数auth的返回值,之后无论在哪里调用,返回值都是200

side_effect=[TimeoutError, 200] 分两次给mock掉的函数auth传值,第一次传一个异常TimeoutError,第一次传 200给auth,请看pay函数的代码,超时会再掉一次。

最后跑一下单元测试 看看结果

test_pay.py::PaymentTest::test_2_fail 
test_pay.py::PaymentTest::test_3_retry_success 
test_pay.py::PaymentTest::test_4_retry_fail 

============================== 4 passed in 0.20s ==============================

Process finished with exit code 0
PASSED                          [ 25%][1001]支付[5000000]成功!!! 进行扣款并登记支付记录
PASSED                             [ 50%][1001]支付[5000000]失败!!! 不进行扣款
PASSED                    [ 75%][1001]支付[5000000]成功!!! 进行扣款并登记支付记录
PASSED                       [100%][1001]支付[5000000]失败!!! 不进行扣款

mock装饰器

一共两种格式

  1. @patch('module名字.方法名')
  2. @patch.object(类名, '方法名')
 1 # 装饰类演示
 2 from mock import Mock, patch
 3 
 4 
 5 # 单独的相乘函数
 6 def multiple(a, b):
 7     return a * b
 8 
 9 
10 # 单独的捕获Exception函数
11 def is_error():
12     try:
13         os.mkdir("11")
14         return False
15     except Exception as e:
16         return True
17 
18 
19 # 计算类,包含add方法
20 class calculator(object):
21     def add(self, a, b):
22         return a + b
23 
24 
25 # 装饰类演示 - 单元测试类
26 class TestProducer(unittest.TestCase):
27 
28     # case执行前
29     def setUp(self):
30         self.calculator = calculator()
31 
32     # mock一个函数,注意也要指定module
33     @patch('mock_learn.multiple')
34     def test_multiple(self, mock_multiple):
35         mock_multiple.return_value = 3
36         self.assertEqual(multiple(8, 14), 3)
37 
38     # mock一个类对象的方法
39     @patch.object(calculator, 'add')
40     def test_add(self, mock_add):
41         mock_add.return_value = 3
42         self.assertEqual(self.calculator.add(8, 14), 3)
43 
44     # mock调用方法返回多个不同的值
45     @patch.object(calculator, 'add')
46     def test_effect(self, mock_add):
47         mock_add.side_effect = [1, 2, 3]
48         self.assertEqual(self.calculator.add(8, 14), 1)
49         self.assertEqual(self.calculator.add(8, 14), 2)
50         self.assertEqual(self.calculator.add(8, 14), 3)
51 
52     # mock的函数抛出Exception
53     @patch('os.mkdir')
54     def test_exception(self, mkdir):
55         mkdir.side_effect = Exception
56         self.assertEqual(is_error(), True)
57 
58     # mock多个函数,注意函数调用顺序
59     @patch.object(calculator, 'add')
60     @patch('mock_learn.multiple')
61     def test_more(self, mock_multiple, mock_add):
62         mock_add.return_value = 1
63         mock_multiple.return_value = 4
64         self.assertEqual(self.calculator.add(3, 3), 1)
65         self.assertEqual(multiple(3, 3), 4)

4. Mock服务

在接口测试的时候,还有一种mock服务。

本质上mock服务就是一个web应用程序,可以自定义接口地址,发送参数,以及响应 结果,实现技术简单。

目前市面上有无数产品,大多数都可以免费使用。