在日常的接口测试中,测试人员常常会遇到以下几种令人头疼的情况:
-
场景一:依赖的接口状态不稳定,导致集成 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 需求整理
根据以上三个场景,加之适用于有赞测试环境模式,可归纳为大致六个需求:
- 调用依赖接口时能够返回指定值( mock 的基本功能)
- 支持同时 mock 多个服务
- 多个测试用例依赖同一个接口,运行时支持返回不同结果
- 支持集成在日常 CI 中使用
- 在一个测试套件中只有部分用例需要 mock 接口 A,其他用例则需要正常的结果返回
- 支持有赞 service chain 路由环境模式调用
三.python中的mock的使用
在Python2.x 中 mock是一个单独模块,需要单独安装。
pip install -U mock
在Python3中,mock已经被集成到了unittest单元测试框架中,可以直接使用。
实例:
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()
再次执行测试用例,结果通过!