嘲弄和存根–了解Mockito的测试双打

介绍

我遇到的一件事是使用模拟框架的团队假设他们在模拟。
他们并不知道Mocks只是Gerard Meszaros在xunitpatterns.com上归类的“测试双打”之一。

重要的是要意识到每种类型的测试双精度在测试中都扮演着不同的角色。 用与您需要学习不同模式或重构的方式相同,您需要了解每种类型的测试double的原始角色。 然后可以将它们组合起来以满足您的测试需求。
我将简要介绍这种分类的产生方式以及每种类型的不同之处。
我将在Mockito中使用一些简短的简单示例进行此操作。

非常简短的历史

多年来,人们一直在编写系统组件的轻量级版本以帮助进行测试。 通常将其称为存根。 在2000年的文章“ Endo-Testing:使用模拟对象进行单元测试”中介绍了模拟对象的概念。 从那时起,Meszaros将存根,模拟和其他许多类型的测试对象归类为“测试双打”。
该术语已由Martin Fowler在“ Mocks Are n't Stubs”中引用,并在Microsoft社区中被采用,如“ Exploring The Test Doubles Continuum of Test Doubles”中所示。
参考部分中显示了指向这些重要论文的链接。

考试双打的类别

上图显示了常用的双重测试类型。 以下URL提供了对每个模式及其功能以及替代术语的很好的交叉引用。
http://xunitpatterns.com/Test%20Double.html

莫基托

Mockito是一个测试间谍框架,学习起来非常简单。 Mockito值得注意的是,在测试之前没有定义任何模拟对象的期望,因为它们有时在其他模拟框架中也是如此。 开始嘲笑时,这会导致更自然的样式(IMHO)。
以下示例在此处纯粹是为了简单演示如何使用Mockito实施不同类型的测试双打。

网站上有大量有关如何使用Mockito的特定示例。
http://docs.mockito.googlecode.com/hg/latest/org/mockito/Mockito.html

使用Mockito测试双打

以下是一些使用Mockito的基本示例,以显示Meszaros定义的每个测试双打的作用。
我为每个对象都提供了指向主要定义的链接,因此您可以获得更多示例和完整定义。

虚拟对象

http://xunitpatterns.com/Dummy%20Object.html
这是所有测试双打中最简单的一次。 这是一个没有实现的对象,仅用于填充与测试无关的方法调用的参数。

例如,下面的代码使用很多代码来创建客户,这对测试并不重要。
只要客户数恢复为1,该测试就不会在乎添加哪个客户。

public Customer createDummyCustomer() {County county = new County('Essex');City city = new City('Romford', county);Address address = new Address('1234 Bank Street', city);Customer customer = new Customer('john', 'dobie', address);return customer;
}@Test
public void addCustomerTest() {Customer dummy = createDummyCustomer();AddressBook addressBook = new AddressBook();addressBook.addCustomer(dummy);assertEquals(1, addressBook.getNumberOfCustomers());
}

我们实际上并不关心客户对象的内容,但是它是必需的。 我们可以尝试使用null值,但是如果代码正确,则可能会引发某种异常。

@Test(expected=Exception.class)
public void addNullCustomerTest() {Customer dummy = null;AddressBook addressBook = new AddressBook();addressBook.addCustomer(dummy);
}

为了避免这种情况,我们可以使用一个简单的Mockito假人来获得所需的行为。

@Test
public void addCustomerWithDummyTest() {Customer dummy = mock(Customer.class);AddressBook addressBook = new AddressBook();addressBook.addCustomer(dummy);Assert.assertEquals(1, addressBook.getNumberOfCustomers());
}

正是这个简单的代码创建了一个要传递给调用的虚拟对象。

Customer dummy = mock(Customer.class);

不要被模拟语法所迷惑-这里扮演的角色是虚拟的,而不是模拟的。
区别在于测试双重性的作用,而不是用于创建双重性的语法。

该类可以轻松替代客户类,并使测试非常容易阅读。

测试存根

http://xunitpatterns.com/Test%20Stub.html
测试存根的作用是将受控值返回到要测试的对象。 这些被描述为测试的间接输入。 希望有一个例子可以阐明这意味着什么。

采取以下代码

public class SimplePricingService implements PricingService
{ PricingRepository repository;public SimplePricingService(PricingRepository pricingRepository) {this.repository = pricingRepository;}@Overridepublic Price priceTrade(Trade trade) {return repository.getPriceForTrade(trade);}@Overridepublic Price getTotalPriceForTrades(Collectiontrades) {Price totalPrice = new Price();for (Trade trade : trades){Price tradePrice = repository.getPriceForTrade(trade);totalPrice = totalPrice.add(tradePrice);}return totalPrice;}

SimplePricingService具有一个协作对象,即交易存储库。 交易存储库通过getPriceForTrade方法将交易价格提供给定价服务。
为了测试SimplePricingService中的业务逻辑,我们需要控制这些间接输入
即我们从未通过测试的输入。 如下所示。

在以下示例中,我们对PricingRepository存根以返回可用于测试SimpleTradeService的业务逻辑的已知值。

@Test
public void testGetHighestPricedTrade() throws Exception {Price price1 = new Price(10); Price price2 = new Price(15);Price price3 = new Price(25);PricingRepository pricingRepository = mock(PricingRepository.class);when(pricingRepository.getPriceForTrade(any(Trade.class))).thenReturn(price1, price2, price3);PricingService service = new SimplePricingService(pricingRepository);Price highestPrice = service.getHighestPricedTrade(getTrades());assertEquals(price3.getAmount(), highestPrice.getAmount());
}

破坏者的例子

测试存根有两种常见的变体:响应者和破坏者。
如前面的示例,使用响应者来测试幸福的道路。
破坏者用于测试以下异常行为。

@Test(expected=TradeNotFoundException.class)
public void testInvalidTrade() throws Exception {Trade trade = new FixtureHelper().getTrade();TradeRepository tradeRepository = mock(TradeRepository.class);when(tradeRepository.getTradeById(anyLong())).thenThrow(new TradeNotFoundException());TradingService tradingService = new SimpleTradingService(tradeRepository);tradingService.getTradeById(trade.getId());
}

模拟对象

http://xunitpatterns.com/Mock%20Object.html
模拟对象用于在测试期间验证对象行为。 通过对象行为,我的意思是我们检查在运行测试时是否在对象上执行了正确的方法和路径。 这与存根的支持作用完全不同,存根用于为您要测试的任何内容提供结果。 在存根中,我们使用为方法定义返回值的模式。

when(customer.getSurname()).thenReturn(surname);

在模拟中,我们使用以下形式检查对象的行为。

verify(listMock).add(s);

这是一个简单的示例,我们要测试新交易是否已正确审核。
这是主要代码。

public class SimpleTradingService implements TradingService{TradeRepository tradeRepository;AuditService auditService;public SimpleTradingService(TradeRepository tradeRepository, AuditService auditService){this.tradeRepository = tradeRepository;this.auditService = auditService;}public Long createTrade(Trade trade) throws CreateTradeException {Long id = tradeRepository.createTrade(trade);auditService.logNewTrade(trade);return id;
}

以下测试为交易存储库创建存根,并为AuditService创建模拟
然后,我们在模拟的AuditService上调用verify,以确保TradeService调用了
logNewTrade方法正确

@Mock
TradeRepository tradeRepository;@Mock
AuditService auditService;@Test
public void testAuditLogEntryMadeForNewTrade() throws Exception { Trade trade = new Trade('Ref 1', 'Description 1');when(tradeRepository.createTrade(trade)).thenReturn(anyLong()); TradingService tradingService = new SimpleTradingService(tradeRepository, auditService);tradingService.createTrade(trade);verify(auditService).logNewTrade(trade);
}

以下行对模拟的AuditService进行检查。

verify(auditService).logNewTrade(trade);

该测试使我们能够证明审计服务在创建交易时的行为正确。

测试间谍

http://xunitpatterns.com/Test%20Spy.html
值得一看上面的链接,以严格定义测试间谍。
但是,在Mockito中,我喜欢使用它来包装实际对象,然后验证或修改其行为以支持您的测试。 这是一个示例,我们检查了列表的标准行为。 注意,我们既可以验证是否调用了add方法,也可以断言该项目已添加到列表中。

@Spy
List listSpy = new ArrayList();@Test
public void testSpyReturnsRealValues() throws Exception {String s = 'dobie';listSpy.add(new String(s));verify(listSpy).add(s);assertEquals(1, listSpy.size());
}

将其与仅可验证方法调用的模拟对象进行比较。 因为我们仅模拟列表的行为,所以它不记录已添加项目,并且在调用size()方法时返回默认值零。

@Mock
ListlistMock = new ArrayList();@Test
public void testMockReturnsZero() throws Exception {String s = 'dobie';listMock.add(new String(s));verify(listMock).add(s);assertEquals(0, listMock.size());
}

testSpy的另一个有用功能是能够对返回调用进行存根。 完成此操作后,该对象将正常运行,直到调用存根方法为止。
在此示例中,我们将get方法存根以始终引发RuntimeException。 其余行为保持不变。

@Test(expected=RuntimeException.class)
public void testSpyReturnsStubbedValues() throws Exception {listSpy.add(new String('dobie'));  assertEquals(1, listSpy.size());when(listSpy.get(anyInt())).thenThrow(new RuntimeException());listSpy.get(0);
}

在此示例中,我们再次保留了核心行为,但更改了size()方法,使其最初返回1,对所有后续调用返回5。

public void testSpyReturnsStubbedValues2() throws Exception {int size = 5;when(listSpy.size()).thenReturn(1, size);int mockedListSize = listSpy.size();assertEquals(1, mockedListSize);mockedListSize = listSpy.size();assertEquals(5, mockedListSize);  mockedListSize = listSpy.size();assertEquals(5, mockedListSize);  
}

这真是不可思议!

假物件

http://xunitpatterns.com/Fake%20Object.html
假物品通常是手工制作或重量较轻的物品,仅用于测试,不适合生产。 一个很好的例子是内存数据库或伪造的服务层。

它们往往提供比标准测试倍增功能更多的功能,因此通常不适合使用Mockito进行实现。 这并不是说它们不能像这样构造,仅仅是因为它可能不值得采用这种方式来实现。

参考:“ 小事半解 ” – 敏捷工程技术博客上来自我们JCG合作伙伴 John Dobie的Mockito了解测试双打 。


翻译自: https://www.javacodegeeks.com/2012/05/mocks-and-stubs-understanding-test.html

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

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

相关文章

numpy 辨异(三)—— hstack/column_stack,linalg.eig/linalg.eigh

1. np.hstack np.column_stack >>> np.hstack([np.array([1, 2, 3]), np.array([4, 5, 6])]) array([1, 2, 3, 4, 5, 6])>>> np.column_stack([np.array([1, 2, 3]), np.array([4, 5, 6])]) array([[1, 4],[2, 5],[3, 6]]) 当然对等地,也存在&…

【代码笔记】iOS-首页3张图片变化

一&#xff0c;效果图。 二&#xff0c;工程图。 三&#xff0c;代码。 RootViewController.h #import <UIKit/UIKit.h>interface RootViewController : UIViewController {NSTimer *timer;UIImageView *imageView1;UIImageView *imageView2;UIImageView *imageView3;UIV…

acwing算法提高之动态规划--数位DP

目录 1 基础知识2 模板3 训练 1 基础知识 暂无。。。 2 模板 暂无。。。 3 训练 题目1&#xff1a;度的数量。 解题思路&#xff1a;分类讨论。 C代码如下&#xff0c; #include <iostream> #include <vector>using namespace std;const int N 35; int K,…

python 输入数字变成密码_如何在python中检查数字的“密码”

我建议使用sets和stdlib中的string包作为可接受字符的列表。在我还建议进行一点重构&#xff0c;以删除大量带有if / else分支的嵌套。在import stringupper set(list(string.uppercase))lower set(list(string.lowercase))numbers set(list(string.digits))while True:npw …

使用Eclipse在Amazon Ec2中部署Java Web应用程序的完整指南

嗨&#xff0c;读者们&#xff0c; 今天&#xff0c;我将向您展示如何使用Eclipse IDE在Amazon EC2中部署简单的Java Web应用程序。 在我们开始之前&#xff0c;我们需要一些必需的东西&#xff0c; Eclipse Java EE IDE –您可以从http://www.eclipse.org/downloads/下载&…

jquery的load方法

load方法指定一个界面会显示在目标的标签内部 比如MVC的一个分部视图页面想要显示在某个标签里面&#xff0c;可以写成 $(标签ID).load&#xff08;分部视图名称,data&#xff09; 其中第二个参数可选&#xff0c;主要是一些需要传递到该页面的数据JSON格式组成&#xff0c;发送…

android 录音原始文件_音频采集:Android基于AudioRecord的实现

前言这篇文章简单介绍下移动端Android系统下利用AudioRecord进行音频采集方法。按照惯例开始前先提供一份源码 AudioRecordLib 。AudioRecord采集的核心实现在于 AudioRecordCore.java 这个文件。权限申请想要使用AudioRecord这个API&#xff0c;需要在AndroidManifest.xml的配…

Spring 3和Java EE 6 –不公平和不完整的比较

这篇小文章的初稿标题为“ Spring&#xff06;Java EE –比较苹果和橙子”。 在撰写本文时&#xff0c;我了解到可以比较Spring Framework和Java EE&#xff0c;但这始终是不公平且不完整的工作。 Java for Enterprise和Spring Framework的发展紧密地联系在一起。 两者相互依存…

xml配置文件推荐方式

1.XML帮助类 /// <summary>/// Xml帮助类/// </summary>public class XmlHelper{/// <summary>/// 保存xml/// </summary>/// <typeparam name"T"></typeparam>/// <param name"path"></param>/// <p…

AFNetWorking https SSL认证

一般来讲如果app用了web service , 我们需要防止数据嗅探来保证数据安全.通常的做法是用ssl来连接以防止数据抓包和嗅探 其实这么做的话还是不够的 。 我们还需要防止中间人攻击&#xff08;不明白的自己去百度&#xff09;。攻击者通过伪造的ssl证书使app连接到了伪装的假冒的…

查看环境列表_Xfce 4.14桌面环境正式发布,想要图形界面又想节省内存?就它了...

1. Xfce 4.14桌面环境正式发布&#xff0c;它有什么新特性&#xff1f;本文主要讲解Xfce 4.14桌面环境正式发布&#xff0c;它有什么新特性。Xfce已经开发了4年多&#xff0c;但是这个周末终于看到了期待已久的Xfce 4.14的发布。Xfce 4.14是这个轻量级桌面环境的最新稳定版本&a…

使用Log4jdbc记录JDBC操作

当我们开发任何应用程序&#xff0c;完成它或结束其任何模块时&#xff0c;我们都会开始优化过程。 大多数应用程序都包含数据库访问权限&#xff0c;并且如果您使用的是ORM &#xff0c;则可能会使用hibernate 。 优化休眠持久层&#xff0c;要求准备阅读&#xff0c;理解和评…

android BluetoothAdapter蓝牙BLE扫描总结

做室内定位的程序员应该都知道&#xff0c;在Android 5.0之后&#xff0c;google推出了蓝牙扫描新接口&#xff0c;我们在实测中发现出一些问题&#xff0c;现在给大家列出&#xff0c;以供参考&#xff1a; 1.android 4.3.1(Build.VERSION_CODES.JELLY_BEAN_MR2)增加的startLe…

卷积神经网络语音识别_用于物体识别的3D卷积神经网络

本文提出了一种基于CNN的3D物体识别方法&#xff0c;能够从3D图像表示中识别3D物体&#xff0c;并在比较了不同的体素时的准确性。已有文献中&#xff0c;3D CNN使用3D点云数据集或者RGBD图像来构建3D CNNs&#xff0c;但是CNN也可以用于直接识别物体体积表示的体素。本文中&am…

段落排版--对齐(text-aliagn)

想为块状元素中的文本、图片设置居中样式吗&#xff1f;可以使用text-align样式代码&#xff0c;如下代码可实现文本居中显示。(那么什么是块状元素呢&#xff1f;后面会讲到呢~) h1{text-align:center; } <h1>了不起的盖茨比</h1> 同样可以设置居左&#xff1a; h…

Java 7:在不丢失数据的情况下关闭NIO.2文件通道

关闭异步文件通道可能非常困难。 如果您将I / O任务提交到异步通道&#xff0c;则需要确保正确执行了任务。 实际上&#xff0c;出于多种原因&#xff0c;这对于异步通道可能是一个棘手的要求。 默认的通道组使用守护进程线程作为工作线程&#xff0c;这不是一个好选择&#xf…

JavaScript封装方法,兼容参数类型为Number和String

/*** 依据Kind确定跳转到目标列表页面。* param kind*/function gobackByKind(kind) {var kindStr String(kind);switch(kindStr){case "1"://跳转到客户列表页面window.location.href/biz/customer/list;break;case "2"://跳转到代理机构列表页面window.…

#获得请求来源ip_以太网数据包TCP、IP、ICMP、UDP、ARP协议头结构详解

以太网首部目地MAC地址(8字节)源MAC地址(8字节)类型(2字节)1、IP头的结构版本(4位)头长度(4位)服务类型(8位)封包总长度(16位)封包标识(16位)标志(3位)片断偏移地址(13位)存活时间(8位)协议(8位)校验和(16位)来源IP地址(32位)目的IP地址(32位)选项(可选)填充(可选)数据(1)字节和…

团队项目第二次冲刺Ⅶ

今天将整体代码的编码方式改了&#xff0c;作业模块基本修改完成 遇到的问题是对于添加问答模块无从下手转载于:https://www.cnblogs.com/brucekun/p/5573312.html

编写Play 2的模块,第2部分:拦截器

在本教程的第一部分中&#xff0c;我们介绍了创建&#xff0c;发布和调用模块的基本知识。 我们创建的模块并没有真正做很多事情&#xff0c;因此现在是时候使用Play的某些功能来扩展功能了。 1.拦截器 拦截器使您可以拦截对控制器的调用&#xff0c;并增强或阻止其行为。 在第…