引言

在日常的接口测试中,测试人员常常会遇到以下几种令人头疼的情况:

  • 场景一:依赖的接口状态不稳定,导致集成 CI 常常失败,需要耗费大量时间排查非被测目标本身之外的环境问题

  • 场景二:做异常测试时构造异常数据成本高、难度大,某些异常数据甚至无法通过正常途径构造

  • 场景三:被测目标开发进度先于依赖模块,当测试需要先行介入接口测试,但依赖模块接口尚且不通

面对以上痛点,我们需要做什么?

一.概述

1.1 Mock 定义

Mock 是测试过程中中常见的一种技术,即对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法,从而把测试与测试边界以外的对象隔离开。

引用《淘宝网-接口测试白皮书》中的对 Mock 的定义

在测试当中,mock 是指使用各种技术手段模拟出各种需要的资源以供测试使用。

被 mock 的资源通常有以下特征:

  • 被测目标依赖该资源
  • 该资源可能因为各种原因不稳定、返回结果不断变化或者并不总是能够获取到
  • 该资源跟被测目标本身质量无关

这些资源可能是一个外部或底层接口、一个系统、一组数据对象或者是一整套目标软件 的工作环境等。通过 mock 避免对外部真实资源的依赖实现对被测目标的孤立测试,从而大 大降低测试的难度,节约测试成本。

1.2 Mock 分类

测试中的 Mock 可以大致分为两类:mock 数据和 mock 服务。

  • Mock 数据即 mock 一个对象,写入一些预期的值,通过它进行自己想要的测试。主要适用于单元测试,例如常见的 EasyMock、Mockito 等。
  • Mock 服务即 mock 一个 sever,构造一个依赖的服务并给予他预期的服务返回值,适用范围广,更加适合我们的集成测试。因此我们就 mock server 这种模式进行设计与开发。
二.需求分析

2.1 核心场景

场景一:小明要测试一个普通商品创建订单接口 create,在创建订单的过程中会交易系统会调用店铺系统查询店铺状态接口 queryShop 和营销系统查询营销活动接口 queryUmp,但这两个接口不太稳定,导致订单创建用例常常失败,小明希望每次调用这两个接口都能直接返回200不影响下单流程;

场景二:小李也在测试订单创建接口,并且依赖店铺查询接口 queryShop 设计了三种店铺状态下的下单场景:店铺正常营业、店铺已打烊、店铺已过试用期未缴费。但是通过正常途径构造测试数据成本很高,需要创建三个店铺->设置店铺状态->创建商品->创建订单。小李希望三个用例调用店铺 query 接口时能返回预期的三个结果;

场景三:碰巧小红也在测试订单创建接口,他们的用例都集成在同一个 CI 中,但是小红的用例中商品参加了某个营销活动,她希望自己的用例访问营销活动查询接口 queryUmp 时能返回正常的结果,不被 mock 用例所影响。

2.2 需求整理

根据以上三个场景,加之适用于有赞测试环境模式,可归纳为大致六个需求:

  1. 调用依赖接口时能够返回指定值( mock 的基本功能)
  2. 支持同时 mock 多个服务
  3. 多个测试用例依赖同一个接口,运行时支持返回不同结果
  4. 支持集成在日常 CI 中使用
  5. 在一个测试套件中只有部分用例需要 mock 接口 A,其他用例则需要正常的结果返回
  6. 支持有赞 service chain 路由环境模式调用

三.python中的mock的使用

在Python2.x 中 mock是一个单独模块,需要单独安装。

pip install -U mock

在Python3中,mock已经被集成到了unittest单元测试框架中,可以直接使用。

实例:

新建addition.py 这里要实现一个count计算类,add() 方法要实现两数相加。但是这个功能我还没有完成。这时就可以借助mock对其进行测试。
class Count():

    def add(self):
        pass

新建mock_demo1.py

from unittest import mock
import unittest

from day04.modular import Count

class TestCount(unittest.TestCase):

    def test_add(self):
        # 调用被测试类Count()
        count = Count()
        # 通过mock类模拟被调用的方法add方法,return_value定义的是add方法的返回值
        count.add = mock.Mock(return_value=10)
        #这个就是正常的调用add方法,传入两个参数8和2,相加的结果是10,10的结果是我们在上一步就预习设定好的
        result = count.add(8, 2)
        #断言,看是否符合预期结果
        self.assertEqual(result, 10)


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

完成功能测试

addition.py

class Count:
    def add(self, a, b):
        return a + b

mock_demo2.py

from unittest import mock
import unittest

from day04.modular import Count

class TestCount(unittest.TestCase):

    def test_add(self):
        # 调用被测试类Count()
        count = Count()
        # 通过mock类模拟被调用的方法add方法,return_value定义的是add方法的返回值
        count.add = mock.Mock(return_value=10)
        #这个就是正常的调用add方法,传入两个参数8和2,相加的结果是10,10的结果是我们在上一步就预习设定好的
        result = count.add(8, 2)
        #断言,看是否符合预期结果
        self.assertEqual(result, 10)


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

使用mock避免测试依赖

有这样一个场景,我们要测试A模块,然后A模块依赖于B模块的调用。但是,由于B模块的改变,导致了A模块返回结果的改变,从而使A模块的测试用例失败。其实,对于A模块,以及A模块的用例来说,并没有变化,不应该失败才对。这个时候就是mock发挥作用的时候了。通过mock模拟掉影响A模块的部分(B模块)。至于mock掉的部分(B模块)应该由其它用例来测试。

新建f1.py

def add_multiply(x, y):
    add = x + y
    mty = multiply(x, y)
    return (add, mty)

def multiply(x, y):
    return x * y

编写f1_test.py用来测试add_multiply函数

import unittest
from day04.f1 import add_multiply

class MyTestCase(unittest.TestCase):
    def test_add_multiply(self):
        x = 6
        y = 4
        add, mty = add_multiply(x, y)
        self.assertEqual(10, add)
        self.assertEqual(24, mty)


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

毫无疑问,运行结果当前是正常的。然而,add_multiply()函数依赖了multiply()函数的返回值。如果这个时候修改multiply()函数的代码。

def multiply(x, y):
    return x * y - 4

这个时候,multiply()函数返回的结果变成了x*y-4,再次运行测试结果当前就是失败的,结果如下:

FAILED                     [100%]
 
20 != 24

Expected :24
Actual   :20
<Click to see difference>

self = <day04.f1_test.MyTestCase testMethod=test_add_and_multiply>

    def test_add_and_multiply(self):
        x = 6
        y = 4
        add, mty = add_multiply(x, y)
        self.assertEqual(10, add)
>       self.assertEqual(24, mty)

f1_test.py:11: 

那么这个问题怎么解决呢?测试用例运行失败了,然而,add_multiply()函数以及它的测试用例并没有做任何修改,罪魁祸首是multiply()函数引起的,所以应该把 multiply()函数mock掉。

import unittest
from unittest.mock import patch
import day04.f1


class MyTestCase(unittest.TestCase):
    #patch装饰使得上下文管理器可以用来模拟类或者对象在模块测试,指定的对象会被替换为一个模拟对象,在测试结束时还原,这里模拟的是day04.f1.multiply
    @patch("day04.f1.multiply")      #这里一定注意需要 模块.函数 这种调用方式,否则会出错
    #在测试用例中,将mock的multiply()函数(对象)重命名为 mock_multiply对象
    def test_add_multiply2(self, mock_multiply):
        x = 6
        y = 4
        #设定mock_multiply对象的返回值固定为24
        mock_multiply.return_value = 24
        addition, multiple = day04.f1.add_and_multiply(x, y)
        #检查mock_multiply给传入的参数是否正确
        mock_multiply.assert_called_once_with(6, 4)
        #断言
        self.assertEqual(10, addition)
        self.assertEqual(24, multiple)


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

再次执行测试用例,结果通过!