大牛们天天说的接口Mock测试,今天我终于搞懂了,Mock方法介绍

大雄 579 2022-08-06


本文是关于mock-接口Mock测试

01

关于Mock测试

0 1

含义和目的

1、什么是Mock测试?

Mock 测试就是在测试过程中,对于某些不容易构造(如 HttpServletRequest 必须在Servlet 容器中才能构造出来)或者不容易获取的比较复杂的对象(如 JDBC 中的ResultSet 对象), 用一个虚拟的对象(Mock 对象)来创建以便测试的测试方法。

2、为什么要进行Mock测试?

Mock是 为了解决不同的单元之间由于耦合而难于开发、测试的问题。所以,Mock既能出现在单元测试中,也会出现在集成测试、系统测试过程中。Mock 最大的功能是帮你 把单元测试的耦合分解开,如果你的代码对另一个类或者接口有依赖,它能够帮你模拟这些依赖,并帮你验证所调用的依赖的行为。

比如一段代码有这样的依赖:

当我们需要测试A类的时候,如果没有 Mock,则我们需要把整个依赖树都构建出来,而使用 Mock 的话就可以将结构分解开,像下面这样:

0 2

Mock对象适用场景

  • 需要将当前被测单元和其依赖模块独立开来,构造一个独立的测试环境,不关注被测单元的依赖对象,只关注被测单元的功能逻辑。

比如被测代码中需要依赖第三方接口返回值进行逻辑处理,可能因为网络或者其他环境因素,调用第三方经常会中断或者失败,无法对被测单元进行测试,这个时候就可以使用mock技术来将被测单元和依赖模块独立开来,使得测试可以进行下去。

  • 被测单元依赖的模块尚未开发完成,而 被测单元需要依赖模块的返回值进行后续处理。 ① 前后端项目中,后端接口开发完成之前,接口联调; ② 依赖的上游项目的接口尚未开发完成,需要接口联调测试;

比如service层的代码中,包含对Dao层的调用,但是,DAO层代码尚未实现

  • 被测单元依赖的对象较难模拟或者构造比较复杂。

比如支付宝支付的异常条件有很多,但是模拟这种异常条件很复杂或者无法模拟,比如,查询聚划算的订单结果,无法在测试环境进行模拟。

0 3

Mock测试的优势

1、团队可以并行工作

有了Mock,前后端人员只需要定义好接口文档就可以开始并行工作,互不影响,只在最后的联调阶段往来密切;后端与后端之间如果有接口耦合,也同样能被Mock解决;测试过程中如果遇到依赖接口没有准备好,同样可以借助Mock;不会出现一个团队等待另一个团队的情况。这样的话,开发自测阶段就可以及早开展,从而发现缺陷的时机也提前了,有利于整个产品质量以及进度的保证

2、开启TDD模式,即测试驱动开发

单元测试是 TDD实现的基石,而TDD经常会碰到协同模块尚未开发完成的情况,但是有了mock,这些一切都不是问题。当接口定义好后,测试人员就可以创建一个Mock,把接口添加到自动化测试环境,提前创建测试。

3、可以模拟那些无法访问的资源

比如说,你需要调用一个“墙”外的资源来方便自己调试,就可以自己Mock一个。

4、隔离系统

假如我们需要调用一个post请求,为了获得某个响应,来看当前系统是否能正确处理返回的“响应”,但是这个post请求会造成数据库中数据的污染,那么就可以充分利用Mock,构造一个虚拟的post请求,我们给他指定返回就好了。

5、可以用来演示

假如我们需要创建一个演示程序,并且做了简单的UI,那么在完全没有开发后端服务的情况下,也可以进行演示。说到演示了,假如你已经做好了一个系统,并且需要给客户进行演示,但是里面有些真实数据并不想让用户看到,那么同样,你可以用 Mock接口把这些敏感信息接口全部替换。

6、测试覆盖度

假如有一个接口,有100个不同类型的返回,我们 需要测试它在不同返回下,系统是否能够正常响应,但是有些返回在正常情况下基本不会发生,比如,我们需要测试在当接口发生500错误的时候,app是否崩溃,别告诉我你一定要给服务端代码做些手脚让他返回500 。而使用mock,这一切就都好办了,想要什么返回就模拟什么返回,不用再担心我的测试覆盖度了!

0 4

Mock测试存在的问题

使用Mock测试有时可以提高团队的开发效率,但当B、C都开发完成代码后,这时应该把E2E测试代码从使用Mock测试改为调用真实的模块,以避免出现模块之间集成部分漏测的问题。这里说mock存在的问题,主要是 让开发和测试不要过分的依赖/相信mock接口。

使用mock时,切记的几点:

  • 测试人员不应该被覆盖率高的E2E自动化测试所迷惑, 覆盖率高不代表没有问题。 尤其在接手新项目中,需要查看E2E测试中有没有使用Mock测试,进一步去判断这些地方使用Mock测试是否合理,这些Mock测试是否应该换成真实模块间的调用和集成。

  • 当把mock接口换成实际接口后,测试/开发也必须把之前的测试重新做一遍。

ps:当你使用mock接口来提高效率, 请注意:

你的工作量其实是比 直接只用实际接口 多了 一倍的。如果测试时偷懒,替换成实际接口后,只是简单测试,那么 当实际接口和mock预期接口有差异时,故障便和你相遇了。

建议:mock接口只能主流程联调/ 异常返回测试,不要过分依赖mock接口进行测试。

  • 测试完毕,上线前请一定确保 为了mock而做的相关代码/配置文件的修改,已经完全恢复了。 建议: 上线checklist中条条列出,并上线前review

02

Mock测试方式

0 1

Mock Server-Moco

这是一个jar包,只要执行该jar包,指定配置文件,就可开启一个http服务器提供服务,并且修改配置文件后也无需重启服务,支持动态加载。我使用的是moco-runner-0.10.2-standalone.jar, 运行方式如下:

```java -jar moco-runner-0.10.2-standalone.jar start -p 8080 -c XXX.json```

XXX.json就是我们的mock配置文件,比如:

[

{

" deion": " api 1",

" request" :

{

" method" : " get",

" uri" : " /foo"

},

" response":

{

" json": {" foo":" bar"}

}

}

]

以上就可以实现当我们访问127.0.0.0:8080/foo时,返回一个json为{"foo":"bar"}。

0 2

fiddler

fiddler大家都很熟了,在windows环境可以随便自定义返回内容,但一个很大的缺点是, 它不跨平台,而我们平时的很多场景下是 需要在Linux下进行mock的。

还有一些其他mock工具,大多都是通过编写js代码或者python、java等代码来达到mock目的,此处就不再介绍了。

在选择mock工具时,可参考以下几个方面:

  • 一是数据要好管理, 别让我管理一堆文件;

  • 二是mock接口最好可以设置成和真实接口 完全一致, 这样就只需要切换hosts就可以切换mock接口和真实接口,不需要修改代码;

  • 三是 跨平台, mock接口在windows和Linux下都需要可用。至于跨域、动态加载什么的,这是必须条件。

03

Mock测试示例

使用Fiddler进行Mock测试,这种调试方式 适用于rest接口调试,web界面调试等。

测试工程师在做测试时,也需要服务器返回一些特殊的数据来做测试,使用 Fiddler AutoResponder功能来伪造测试数据(创建虚拟对象),能大大 减少测试工程师的工作量。

1、Fiddler AutoResponder工作原理

使用Fiddler可以替换自动返回的一个【伪造】的HTTP响应,这与使用断点修改HTTP响应类似,只不过 AutoResponder是自动的,操作更加方便。即浏览器发出的HTTP请求并没有到达服务器,而是被Fiddler直接返回了一个【伪造】的HTTP响应。

2、使用Fiddler进行Mock测试

  • 接口抓包-----找到要mock的接口 以掘金首页为例,找到下面的接口 https://gold-tag-ms.juejin.im/v1/categories

  • 复制接口数据到本地 在接口上进行右键点击,选择save -> …and Open as Local File -> 默认会保存至桌面,示例中的数据,保存到了桌面的test.json

  • 修改数据 修改保存到本地的json文件,示例中仅修改了页面的标签数据。

  • 替换json文件 在web session 面板中找到对应的请求,然后将其拖到AutoResponder面板中,在RuleEditor中单击“Find a file...”,选择本地json文件的路径。

  • 激活规则 选中“Enable rules”,激活规则。选中“Unmatched requests passthrough",放行不匹配的HTTP请求。

  • save,刷新页面 单击“Save”按钮。只需修改本地保存的json文件,然后刷新浏览器(或直接访问接口),就可以看到效果了。

1.现有的单元测试框架
单元测试是保证程序正确性的一种有效的测试手段,对于不同的开发语言,通常都能找到相应的单元框架。

借助于这些单测框架的帮助,能够使得我们编写单元测试用例的过程变得便捷而优雅。框架帮我们提供了case的管理,执行,断言集,运行参数,全局事件工作,所有的这些使得我们只需关注:于对于特定的输入,被测对象的返回是否正常。
那么,这些xUnit系列的单元测试框架是如何做到这些的了?分析这些框架,发现所有的单元测试框架都是基于以下的一种体系结构设计的。

如上图所示,单测框架中通常包括TestRunner, Test, TestResult, TestCase, TestSuite, TestFixture六个组件。
TestRuner:负责驱动单元测试用例的执行,汇报测试执行的结果,从而简化测试
TestFixture:以测试套件的形式提供setUp()和tearDown()方法,保证两个test case之间的执行是相互独立,互不影响的。
TestResult:这个组件用于收集每个test case的执行结果
Test:作为TestSuite和TestCase的父类暴露run()方法为TestRunner调用
TestCase:暴露给用户的一个类,用户通过继承TestCase,编写自己的测试用例逻辑
TestSuite:提供suite功能管理testCase
正因为相似的体系结构,所以大多数单元测试框架都提供了类似的功能和使用方法。那么在单测中引入单元测试框架会带来什么好处,在现有单元测试框架下还会存在什么样不能解决的问题呢?


2.单元测试框架的优点与一些问题
在单元测试中引入单测框架使得编写单测用例时,不需要再关注于如何驱动case的执行,如何收集结果,如何管理case集,只需要关注于如何写好单个测试用例即可;同时,在一些测试框架中通过提供丰富的断言集,公用方法,以及运行参数使得编写单个testcase的过程得到了最大的简化。
那这其中会存在什么样的疑问了?
我在单元测试框架中写一个TestCase,与我单独写一个cpp文件在main()方法里写测试代码有什么本质却别吗?用了单元测试框架,并没有解决我在对复杂系统做单测时遇到的问题。
没错,对于单个case这两者从本质上说是没有区别的。单元测试框架本身并没有告诉你如何去写TestCase,在这一点上他是没有提供任何帮助的。所以对于一些复杂的场景,只用单元测试框架是有点多少显得无能为力的。
使用单元测试框架往往适用于以下场景的测试:单个函数,一个class,或者几个功能相关class的测试,对于纯函数测试,接口级别的测试尤其适用,如房贷计算器公式的测试。
但是,对于一些复杂场景:
 被测对象依赖复杂,甚至无法简单new出这个对象
 对于一些failure场景的测试
 被测对象中涉及多线程合作
 被测对象通过消息与外界交互的场景
 …
单纯依赖单测框架是无法实现单元测试的,而从某种意义上来说,这些场景反而是测试中的重点。
以分布式系统的测试为例,class 与 function级别的单元测试对整个系统的帮助不大,当然,这种单元测试对单个程序的质量有帮助;分布式系统测试的要点是测试进程间的交互:一个进程收到客户请求,该如何处理,然后转发给其他进程;收到响应之后,又修改并应答客户;同时分布式系统测试中通常更关注一些异常路径的测试,这些场景才是测试中的重点,也是难点所在。
Mock方法的引入通常能帮助我们解决以上场景中遇到的难题。


3.Mock的引入带来了什么
在维基百科上这样描述Mock:In object-oriented programming, mock objects are simulated objects that mimic the behavior of real objects in controlled ways. A computer programmer typically creates a mock object to test the behavior of some other object, in much the same way that a car designer uses a crash test dummy to simulate the dynamic behavior. of a human in vehicle impacts.
Mock通常是指,在测试一个对象A时,我们构造一些假的对象来模拟与A之间的交互,而这些Mock对象的行为是我们事先设定且符合预期。通过这些Mock对象来测试A在正常逻辑,异常逻辑或压力情况下工作是否正常。
引入Mock最大的优势在于:Mock的行为固定,它确保当你访问该Mock的某个方法时总是能够获得一个没有任何逻辑的直接就返回的预期结果。
Mock Object的使用通常会带来以下一些好处:
 隔绝其他模块出错引起本模块的测试错误。
 隔绝其他模块的开发状态,只要定义好接口,不用管他们开发有没有完成。
 一些速度较慢的操作,可以用Mock Object代替,快速返回。
对于分布式系统的测试,使用Mock Object会有另外两项很重要的收益:
 通过Mock Object可以将一些分布式测试转化为本地的测试
 将Mock用于压力测试,可以解决测试集群无法模拟线上集群大规模下的压力


4.Mock的应用场景
在使用Mock的过程中,发现Mock是有一些通用性的,对于一些应用场景,是非常适合使用Mock的:
 真实对象具有不可确定的行为(产生不可预测的结果,如股票的行情)
 真实对象很难被创建(比如具体的web容器)
 真实对象的某些行为很难触发(比如网络错误)
 真实情况令程序的运行速度很慢
 真实对象有用户界面
 测试需要询问真实对象它是如何被调用的(比如测试可能需要验证某个回调函数是否被调用了)
 真实对象实际上并不存在(当需要和其他开发小组,或者新的硬件系统打交道的时候,这是一个普遍的问题)
当然,也有一些不得不Mock的场景:
 一些比较难构造的Object:这类Object通常有很多依赖,在单元测试中构造出这样类通常花费的成本太大。
 执行操作的时间较长Object:有一些Object的操作费时,而被测对象依赖于这一个操作的执行结果,例如大文件写操作,数据的更新等等,出于测试的需求,通常将这类操作进行Mock。
 异常逻辑:一些异常的逻辑往往在正常测试中是很难触发的,通过Mock可以人为的控制触发异常逻辑。
在一些压力测试的场景下,也不得不使用Mock,例如在分布式系统测试中,通常需要测试一些单点(如namenode,jobtracker)在压力场景下的工作是否正常。而通常测试集群在正常逻辑下无法提供足够的压力(主要原因是受限于机器数量),这时候就需要应用Mock去满足。
在这些场景下,我们应该如何去做Mock的工作了,一些现有的Mock工具可以帮助我们进行Mock工作。


5.Mock工具的介绍
手动的构造 Mock 对象通常带来额外的编码量,而且这些为创建 Mock 对象而编写的代码很有可能引入错误。目前,有许多开源项目对动态构建 Mock 对象提供了支持,这些项目能够根据现有的接口或类动态生成,这样不仅能避免额外的编码工作,同时也降低了引入错误的可能。
C++: GoogleMock http://code.google.com/p/googlemock/

Java: EasyMock http://easymock.org/

通常Mock工具通过简单的方法对于给定的接口生成 Mock 对象的类库。它提供对接口的模拟,能够通过录制、回放、检查三步来完成大体的测试过程,可以验证方法的调用种类、次数、顺序,可以令 Mock 对象返回指定的值或抛出指定异常。通过这些Mock工具我们可以方便的构造 Mock 对象从而使单元测试顺利进行,能够应用于更加复杂的测试场景。
以EasyMock为例,通过 EasyMock,我们可以为指定的接口动态的创建 Mock 对象,并利用 Mock 对象来模拟协同模块,从而使单元测试顺利进行。这个过程大致可以划分为以下几个步骤:
 使用 EasyMock 生成 Mock 对象
 设定 Mock 对象的预期行为和输出 
 将 Mock 对象切换到 Replay 状态
 调用 Mock 对象方法进行单元测试
 对 Mock 对象的行为进行验证
EasyMock的使用和原理: http://www.ibm.com/developerworks/cn/opensource/os-cn-easymock/

EasyMock 后台处理的主要原理是利用 java.lang.reflect.Proxy 为指定的接口创建一个动态代理,这个动态代理,就是我们在编码中用到的 Mock 对象。EasyMock 还为这个动态代理提供了一个 InvocationHandler 接口的实现,这个实现类的主要功能就是将动态代理的预期行为记录在某个映射表中和在实际调用时从这个映射表中取出预期输出。
借助类似于EasyMock这样工具,大大降低了编写Mock对象的成本,通常来说Mock工具依赖于单元测试框架,为用户编写TestCase提供便利,但是本身依赖于单元测试框架去驱动,管理case,以及收集测试结果。例如EasyMock依赖于JUint,GoogleMock依赖于Gtest。
那么有了单元测试框架和相应的Mock工具就万事俱备了,还有什么样的问题?正如单元测试框架没有告诉你如何写TestCase一样,Mock工具也没有告诉你如何去选择Mock的点。


6.如何选择恰当的mock点
对于Mock这里存在两个误区,1.是Mock的对象越多越好;2.Mock会引入巨大的工作量,通常得不偿失。这都是源于不恰当的Mock点的选取。
这里说的如何选择恰当的mock点,是说对于一个被测对象,我们应当在外围选择恰当的mock对象,以及需要mock的接口。因为对于任意一个对象,任意一段代码逻辑我们都是有办法进行Mock的,而Mock点选择直接决定了我们Mock的工作量以及测试效果。从另外一种意义上来说,不恰当Mock选择反而会对我们的测试产生误导,从而在后期的集成和系统测试中引入更多的问题。
在mock点的选择过程中,以下的一些点会是一些不错的选择
 网络交互:如果两个被测模块之间是通过网络进行交互的,那么对于网络交互进行Mock通常是比较合适的,如RPC
 外部资源:比如文件系统、数据源,如果被测对象对此类外部资源依赖性非常强,而其行为的不可预测性很可能导致测试的随机失败,此类的外部资源也适合进行Mock。
 UI:因为UI很多时候都是用户行为触发事件,系统本身只是对这些触发事件进行相应,对这类UI做Mock,往往能够实现很好的收益,很多基于关键字驱动的框架都是基于UI进行Mock的
 第三方API:当接口属于使用者,通过Mock该接口来确定测试使用者与接口的交互。


当然如何做Mock一定是与被系统的特性精密关联的,一些强制性的约束和规范是不合适的。这里介绍几个做的比较好的mock的例子。
1. 杀毒软件更新部署模块的Mock
这个例子源于一款杀毒产品的更新部署模块的测试。对于一个杀毒软件客户端而言,需要通过更新检查模块与病毒库Server进行交互,如果发现病毒库有更新则触发病毒库部署模块的最新病毒库的数据请求和部署工作,要求部署完成后杀毒软件客户端能够正常工作。


对于这一场景的测试,当时受限于这样一个条件,通常的病毒库server通常最多一天只更新一次病毒库,也就是说如果使用真实的病毒库server,那么针对更新部署模块的测试一天只能被触发一次。这是测试中所不能容忍的,通过对病毒库server进行mock可以解决这个问题。
对于这个场景可以采取这样一种Mock方式:用一个本地文件夹来模拟病毒库server,选择更新部署模块与病毒库server之间交互的两个函数checkVersion(),reqData()函数进行Mock。
checkVersion()工作原先的工作是检查病毒库Server的版本号,以决定是否触发更新,将其行为Mock为检查一个本地文件夹中病毒库的版本号;reqData()原有的行为是从病毒库Server拖取病毒库文件,将其Mock为从本地文件夹中拖取病毒库文件。通过这种方式我们用一个本地文件夹Mock病毒库Server的行为,其带来的产出是:我们可以随意的触发病毒库更新操作以及各种异常。通过这种方式发现了一个在更新部署过程中,病毒库Server的病毒库版本发生改变造成出错的严重bug,这个是在原有一天才触发一次更新操作的情况下永远也无法发现的。
2. 分布式系统中对NameNode模块的测试


在测试NameNode模块的过程中存在这样一个问题,在正常逻辑无压力条件下NameNode模块都是工作正常的。但是线上集群在大压力的情况下,是有可能触发NameNode的问题的。但是原有的测试方法下,我们是无法对NameNode模拟大压力的场景的(因为NameNode的压力主要来源于DateNode数量,而我们测试集群是远远无法达到线上几千台机器的规模的),而NameNode单点的性能瓶颈问题恰恰是测试的重点,真实的DataNode是无法满足测试需求的,我们必须对DataNode进行Mock。
 


如何对DateNode进行Mock了,最直观的想法是选择NameNode与DataNode之间的交互接口进行Mock,也就是他们之间的RPC交互,但是由于NameNode与DataNode之间的交互信息种类很多,所以其实这并不是一种很好的选择。
换个角度来想,NameNode之上的压力是源于对HDFS的读写操作造成的NameNode上元数据的维护,也就是说,对于NameNode而言,其实他并不关心数据到底写到哪里去了,只关心数据是否读写成功。如果是这种场景Mock就可以变的简单了,我们可以直接将DataNode上对块的操作进行mock,比如,对一次写请求,DataNode并不触发真实的写操作,而直接返回成功。通过这种方式,DataNode去除了执行功能,只保留了消息交互功能,间接的实现了我们的测试需求,且工作量比之第一种方案小很多。
3. 开源社区提供的MRUnit测试框架
在原有框架下,对于MapReduce程序的测试通常是无法在本地验证的,更不用说对MapReduce程序进行单测了。而MRUnit通过一个简单而优雅的Mock,却实现了一个基于MapReduce程序的单测框架。

基于MRUINT框架可以将单测写成如下形式:


在这个框架中定义了MapDriver,ReducerDriver,MapReduceDriver三个有点类似容器的driver,通过driver来驱动map,reduce或者整个mapreduce过程的执行。
如上例,在driver中设定mapper为IdentityMapper,通过withInput方法设定输入数据,通过withOutput方法设定预期结果,通过runTest方法来触发执行并进行结果检测
他的实现原理是将outputCollector做Mock,outputCollectort中的emit方法实现的逻辑是将数据写到文件系统中,Mock后是通过另外一个进程去收集数据并保存在内存中,从而实现最终结果的可检验(在自己的数据结构中比对结果)。
实现的原理很简单,这样做mock就会精巧,只选择最底层的一些简单却又依赖广泛的点(依赖广泛指模块间的数据流通常都走这样的点过)做mock,这样通常效果很好且简单
当然这个例子中也有一些缺陷:1.因为在outputcollector层做mock的数据截取,使得无法过partition的分桶逻辑;2.这个框架是写内存的,无法最终改成压力性能测试工具。
 

7 附录
1. EasyMock示例:
2. A Brief History of Mock Objects
http://ecmp.baidu.com/page/site/dmsqa/document-details?nodeRef=workspace://SpacesStore/c4e4bd14-aa79-417b-b18a-6502141bb3be&cursor=0&showFolders=all
3. http://www.mockobjects.com/(FQ)

以上就是小编为大家整理的mock-接口Mock测试


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:到底什么是API网关?API安全网关之可视化相关科普
下一篇:从模拟接口到8K传输 显卡接口这些年变得太多,市面上常见的模拟接口有哪些
相关文章

 发表评论

暂时没有评论,来抢沙发吧~