测试双打:模拟,假人和存根

大多数班级都有合作者。 在进行单元测试时,您通常希望避免使用那些协作者的实际实现方式来避免测试的脆弱性和绑定/耦合,而应使用测试双打:模拟,存根和双打。 本文引用了有关该主题的两篇现有文章:Martin Fowler的Mocks Are n't Stubs和Bob Uncle的The Little Mocker 。 我都推荐他们。

术语

我将从Gerard Meszaros的书xUnit Test Patterns中借用一个术语。 在其中,他引入了术语“ 被测系统( SUT )”,即我们正在测试的东西。 “测试中的类”是更适用于面向对象的世界的一种替代方法,但是我会坚持使用SUT,因为Fowler也会这样做。

我还将使用状态验证行为验证这两个术语。 状态验证是通过检查SUT或其协作者的状态来验证代码是否正常工作。 行为验证是在验证协作者是否按照我们期望的方式被调用或调用。

测试双打

好,回到如何与被测系统的合作者打交道。 对于SUT的每个协作者,您可以使用该协作者的实际实现。 例如,如果您有一个与数据访问对象(DAO)协作的服务,如下面的WidgetService示例中所示,则可以使用真实的DAO实现。 但是,它很可能与数据库冲突,这绝对不是我们要进行单元测试所需的数据库。 另外,如果DAO实现中的代码发生更改,则可能导致我们的测试开始失败。 我个人不喜欢当被测代码本身未更改时测试开始失败。

因此,我们可以使用有时称为“测试双打”的测试。 “测试双打”一词也来自Meszaros的xUnit测试模式书。 他将它们描述为“为了明确运行测试而安装的代替实际组件的任何对象或组件”。

在本文中,我将介绍我使用的三种主要的测试双打类型:模拟,存根和傻瓜。 我还将简要介绍两个我很少明确使用的东西:间谍和假货。

1.嘲弄

首先,“模拟”是一个过载的术语。 它通常用作任何测试双精度测试的总称; 也就是说,任何类型的对象都可以代替测试中的类中的真实协作者。 我对此感到满意,因为大多数模拟框架都支持此处讨论的大多数测试双打。 但是,出于本文的目的,我将以更严格,更有限的含义使用模拟。

具体来说, 模拟是一种使用行为验证的测试替身类型

马丁·福勒(Martin Fowler)将模拟描述为“用期望进行预编程的对象,这些对象构成了期望接收的调用的规范”。 正如Bob叔叔所说的那样,模拟程序会监视正在测试的模块的行为,并且知道期望的行为。 一个例子可以使它更清楚。

想象一下WidgetService的实现:

public class WidgetService {final WidgetDao dao;public WidgetService(WidgetDao dao) {this.dao = dao;}public void createWidget(Widget widget) {//misc business logic, for example, validating widget is valid//...dao.saveWidget(widget);}
}

我们的测试可能看起来像这样:

public class WidgetServiceTest {//test fixturesWidgetDao widgetDao = mock(WidgetDao.class);WidgetService widgetService = new WidgetService(widgetDao);Widget widget = new Widget();@Testpublic void createWidget_saves_widget() throws Exception {//call method under testwidgetService.createWidget(widget);//verify expectationverify(widgetDao).saveWidget(widget);}
}

我们创建了一个WidgetDao的模拟,并验证它是否如预期的那样被调用。 我们还可以告诉模拟程序在调用时如何响应。 这是模拟的重要组成部分,允许您操纵模拟,以便可以测试代码的特定单元,但是在这种情况下,测试不是必需的。

模拟框架

在此示例中,我将Mockito用于模拟框架,但Java空间中还有其他对象,包括EasyMock和JMock 。

自己动手玩?

请注意,您不必使用模拟框架即可使用模拟。 您也可以自己编写模拟,甚至可以在模拟中构建断言。 例如,在这种情况下,我们可以创建一个名为WidgetDaoMock的类,该类实现WidgetDao接口,并且该类的createWidget()方法的实现仅记录其被调用的情况。 然后,您可以验证呼叫是否按预期进行。 尽管如此,现代的模拟框架仍然使这种“劳碌自在”的解决方案变得多余。

2.存根

存根是为了测试目的而“存根”或提供实现的大大简化版本的对象。

例如,如果我们的WidgetService类现在也也依赖于ManagerService。 请参阅此处的标准化方法:

public class WidgetService {final WidgetDao dao;final ManagerService manager;public WidgetService(WidgetDao dao, ManagerService manager) {this.dao = dao;this.manager = manager;}public void standardize(Widget widget) {if (manager.isActive()) {widget.setStandardized(true);}}public void createWidget(Widget widget) {//omitted for brevity}
}

并且我们想测试当管理器处于活动状态时,标准化方法是否“标准化”了一个小部件,我们可以使用如下所示的存根:

public class WidgetServiceTest {WidgetDao widgetDao = mock(WidgetDao.class);Widget widget = new Widget();class ManagerServiceStub extends ManagerService {@Overridepublic boolean isActive() {return true;}}@Testpublic void standardize_standardizes_widget_when_active() {//setupManagerServiceStub managerServiceStub = new ManagerServiceStub();WidgetService widgetService = new WidgetService(widgetDao, managerServiceStub);//call method under testwidgetService.standardize(widget);//verify stateassertTrue(widget.isStandardized());}
}

由于模拟通常用于行为验证,而存根可用于状态验证或行为验证。

该示例非常基础,也可以使用模拟来完成,但是存根可以为测试夹具的可配置性提供一种有用的方法。 我们可以对ManagerServiceStub进行参数化,以使其将“活动”字段的值用作构造函数参数,因此可以在否定测试用例中重用。 也可以使用更复杂的参数和行为。 其他选项包括将存根创建为匿名内部类,或为存根创建基类,例如ManagerServiceStubBase,以供其他人扩展。 后者的优点是,如果ManagerService接口发生更改,则只有ManagerServiceStubBase类会中断,并且需要更新。

我倾向于经常使用存根。 我喜欢他们提供的灵活性,以便能够自定义测试装置,并提供纯Java代码提供的清晰度。 将来的维护者不需要能够理解某个框架。 我的大多数同事似乎更喜欢使用模拟框架。 找到最适合您的方法,并运用最佳判断。

3.假人

顾名思义,虚拟对象是非常愚蠢的类。 它几乎不包含任何内容,基本上只足以使您的代码得以编译。 当您不在乎如何使用虚拟对象时,可以将其传递给某些对象。 例如,作为测试的一部分,当您必须传递参数时,但是您不希望使用该参数。

例如,在前面的示例中的standardize_standardizes_widget_when_active()测试中,我们仍然继续使用模拟的WidgetDao。 虚拟对象可能是一个更好的选择,因为我们根本不希望在createWidget()方法中完全使用WidgetDao。

public class WidgetServiceTest {Widget widget = new Widget();class ManagerServiceStub extends ManagerService {@Overridepublic boolean isActive() {return true;}}class WidgetDaoDummy implements WidgetDao {@Overridepublic Widget getWidget() {throw new RuntimeException("Not expected to be called");}@Overridepublic void saveWidget(Widget widget) {throw new RuntimeException("Not expected to be called");}}@Testpublic void standardize_standardizes_widget_when_active() {//setupManagerServiceStub managerServiceStub = new ManagerServiceStub();WidgetDaoDummy widgetDao = new WidgetDaoDummy();WidgetService widgetService = new WidgetService(widgetDao, managerServiceStub);//call method under testwidgetService.standardize(widget);//verify stateassertTrue(widget.isStandardized());}
}

在这种情况下,我创建了一个内部类。 在大多数情况下,由于Dummy功能很少会在测试之间发生变化,因此创建非内部类并为所有测试重用更为有意义。

还要注意在这种情况下,使用模拟框架创建类的模拟实例也是可行的选择。 我个人很少使用假人,而是创建这样的模拟:

WidgetDaoDummy widgetDao = mock(WidgetDao.class);

尽管可以肯定的是,当确实发生意外调用时,抛出异常会更加困难(这取决于您选择的模拟框架),但是它确实具有简洁性的巨大优势。 虚拟变量可能很长,因为它们需要在接口中实现每种方法。

与存根一样,假人可用于状态或行为验证。

间谍与伪造

我将简要介绍另外两种测试双打:间谍和伪造。 我之所以简短地说,是因为我个人很少自己明确使用这两种类型的双打,而且还因为术语可能会引起混乱,而又不会引起更多细微差别! 但是为了完整性……

间谍

当您想确保系统调用了某个方法时,可以使用间谍程序。 它还可以记录各种事情,例如计算调用次数,或记录每次传递的参数。

但是,对于间谍来说,存在将测试与代码实现紧密耦合的危险。

间谍专用于行为验证。

大多数现代的模拟框架也很好地涵盖了这种功能。

假货

马丁·福勒(Martin Fowler)对伪造品的描述如下:伪造品具有有效的实现方式,但通常采取一些捷径,这使其不适合生产(内存数据库是一个很好的例子)。

我个人很少使用它们。

结论

测试双打是单元测试不可或缺的一部分。 嘲笑,存根和双打都是有用的工具,了解它们之间的差异很重要。

从最严格的意义上讲,模拟只是使用行为验证的双精度形式。 指定了两倍的期望值,然后在调用SUT时进行验证。 但是,工作模拟也已经变得越来越笼统地描述了此处描述的任何双打,实际上大多数现代模拟框架都可以这种通用方式使用。

最后,您应该使用哪种双精度型? 这取决于所测试的代码,但是我建议您遵循使您的测试意图最清楚的任何方式进行指导。

资料来源

  • 莫蒂不是存根作者 ,马丁·福勒(Martin Fowler)
  • 小嘲笑 ,“叔叔”鲍勃·马丁
  • xUnit测试模式 ,作者Gerard Meszaros

翻译自: https://www.javacodegeeks.com/2015/11/test-doubles-mocks-dummies-and-stubs.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/356205.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机的iscsi配置,PC端的iSCSI参数设置方法

PC端的iSCSI参数设置方法上面介绍完NAS端的设置,接下来再来给大家介绍一下PC端的iSCSI设置,主要是实现在PC端上连接使用NAS上的iSCSI存储空间,这里我们以Windows 7 SP1系统为例(Windows8上的设置基本与之类似)。1.首先打开控制面板&#xff0…

纸筒制作机器人_5个万圣节小手工,带孩子一起动手制作,简单又有趣!

在西方国家,除了圣诞节,最受期待的节日莫过于是十月底的万圣节了,提起这个节日,这五大元素可以说是不错的体现:搞怪的南瓜、可爱的蜘蛛、萌萌哒蝙蝠、点睛之笔的蜡烛,以及可以用来迎客的门饰。今天我们就来…

一些常用正则表达式片段的分析

前言: 明天就要奔赴上海了,希望是个好的开始,好久没用正则表达式了,还好之前好好学习过,捡起来也是很容易,好了,为了才重回巅峰状态,想要入门的可以给大家推荐几篇文章, …

计算机应用昨早领域,计算机应用-第1章.ppt

计算机应用-第1章课程简介 本课程的主要任务是通过讲课和练习,使同学们明确计算机整体概念。掌握计算机中的数制及其表示和运算、计算机系统的基本组成和基本工作原理、典型系统软件和应用软件的使用,以及网络与多媒体的基础知识。培养大家触类旁通的应用…

原理c++_浅谈C/S和B/S架构的工作原理及优缺点

C/S架构一、C/S架构及其背景C/S架构是一种比较早的软件架构,主要应用于局域网内。在这之前经历了集中计算模式,随着计算机网络的进步与发展,尤其是可视化工具的应用,出现过两层C/S和三层C/S架构,不过一直很流行也比较经…

java笔记之线程方式1启动线程

* 需求:我们要实现多线程的程序。 * 如何实现呢? * 由于线程是依赖进程而存在的,所以我们应该先创建一个进程出来。 * 而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。 * Java是不能直接调用系统功能的,所…

2015年Devoxx比利时–最后的想法

好吧,另一个Devoxx对我而言已经结束了,它必须是第六次中的第五次(或更多次)不记得说实话。 距离我上次上任已经3年了,所以有点回头了。 Devoxx(比利时)正在成长,实际上这是我第一次…

全国职业院校技能大赛软件测试题目,我院荣获2017年全国职业院校技能大赛软件测试赛项一等奖...

6月6日,2017年全国职业院校技能大赛“软件测试”赛项在河南许昌落下帷幕,共有来自全国29个省市(自治区)的87支代表队261名选手参加比赛。由我院计算机系陈爽、张冬雪、侯博睿等三名同学组成的北京市第二代表队荣获大赛一等奖(第五名)。王红霞、温绍洁荣获…

ntp如何确认与服务器偏差_CentOS 8 启用 NTP 服务

NTP 服务是什么和为什么我们需要在我们的服务器启用 NTP 服务?可能很多人都不是非常了解,简单来说就是我们希望我们服务器的时间是准确的没有偏差的。这个的意义在数据插入和你程序取得计算机时间的时候是准确的。比如说在 java 中如果 new date() 将会获…

JavaFX上手--第1天

1.第一个JavaFX Application JavaFX 使用Java来制作可视化图形,可以做动画和3D效果,JavaFX从JDK中直接使用。 package application;import javafx.application.Application; import javafx.stage.Stage; import javafx.scene.Scene; import javafx.scene…

年月日软件测试用例的设计,实验三日期题白盒测试用例设计.doc

实验三日期题白盒测试用例设计日期问题白盒测试用例的设计(实验时间2012.3.21)一、实验目的1.熟练掌握如何运用基路径测试方法进行测试用例设计。二、实验内容1、题目前一日函数PreDate是NextDate的逆函数(代码实现见下),实现功能为:输入1800…

机制 linux_从一道面试题谈linux下fork的运行机制

今天一位朋友去一个不错的外企面试linux开发职位,面试官出了一个如下的题目:给出如下C程序,在linux下使用gcc编译:#include "stdio.h"#include "sys/types.h"#include "unistd.h"int main(){pid_t …

p1417 烹调方案_Java 8的烹调方式–拼图项目

p1417 烹调方案什么是Project Jigsaw:Project Jigsaw是使Java编译器模块知道的项目。 多年以来,Java API一直是单块的,即从代码的任何部分都可以平等地看到整个API。 还没有任何方法可以声明代码对任何其他用户库的依赖关系。 拼图项目试图以…

jQuery progression 表单进度

progression.js是一款表单输入完成进度插件。支持自定义提示框大小、方向、左边、动画效果、间距等&#xff0c;也支持是否显示进度条、字体大小、颜色、背景色等。 在线实例 实例演示 使用方法 <form id"myform"> <p> <label>点击一个…

教学案例 计算机,宁夏计算机教学案例

宁夏计算机教学案例&#xff0c;答辩老师不仔细看**跟他们发现不了你**中的问题根本是两个概念。宁夏计算机教学案例&#xff0c; 生手指从未使用过计算机系统的学习者。他们不熟悉计算机的操作&#xff0c;缺乏有关计算机系统的知识。他们对计算机会产生一种陌生的感觉。新手指…

python读取多个文件夹图片_python或C++读取指定文件夹下的所有图片

本文实例为大家分享了python或C读取指定文件夹下的所有图片&#xff0c;供大家参考&#xff0c;具体内容如下1.python读取指定文件夹下的所有图片路径和图片文件名import cv2from os import walk,pathdef get_fileNames(rootdir):data[]prefix []for root, dirs, files in wal…

您在2016年会做什么? Apache Spark,Kafka,Drill等

让我们玩得开心。 这是新的一年的开始-我们正处于新事物的门槛上-因此让我们期待您在2016年可能会做的事情。现在我知道做出预测的风险&#xff0c;尤其是有记录的预测&#xff0c;但是我很高兴您能在一年后回访&#xff0c;看看我对2016年的预测是如何完成的。 您在2016年会…

话筒好坏测试软件,如何简单地判断麦克风的质量好坏?

如何简单地判断麦克风的质量好坏&#xff1f;麦克风质量好不好&#xff0c;主要看三点&#xff1a;咪芯&#xff0c;线材和外壳。在挑选麦克风时&#xff0c;我们通常都不会一一上手试用&#xff0c;而是通过它自身的规格参数来进行初步地判断&#xff0c;一般来说&#xff0c;…

怎么保证读取最新数据_Kafka怎么保证数据不丢失?

Kafka怎么保证数据不丢失&#xff1f;这个问题要从3个方面来保证数据不丢失&#xff1a;生产者、服务端、消费者。01producer 生产端是如何保证数据不丢失的1.ack的配置策略acks all (或-1)生产者在发送消息之后&#xff0c;需要等待ISR中所有的副本都成功写入消息之后才能够收…

取模和求余的区别

通常情况下取模运算(mod)和求余(rem)运算被混为一谈&#xff0c;因为在大多数的编程语言里&#xff0c;都用%符号表示取模或者求余运算。在这里要提醒大家要十分注意当前环境下%运算符的具体意义&#xff0c;因为在有负数存在的情况下&#xff0c;两者的结果是不一样的。对于整…