使用模拟的单元测试–测试技术5

我的最后一个博客是有关测试代码方法的一系列博客中的第四篇,演示了如何创建使用存根对象隔离测试对象的单元测试。 今天的博客探讨了有时被视为对立的技术:使用模拟对象进行单元测试。 同样,我使用了从数据库检索地址的简单方案:

…并测试AddressService类:

@Component
public class AddressService {private static final Logger logger = LoggerFactory.getLogger(AddressService.class);private AddressDao addressDao;/*** Given an id, retrieve an address. Apply phony business rules.* * @param id*            The id of the address object.*/public Address findAddress(int id) {logger.info("In Address Service with id: " + id);Address address = addressDao.findAddress(id);address = businessMethod(address);logger.info("Leaving Address Service with id: " + id);return address;}private Address businessMethod(Address address) {logger.info("in business method");// Apply the Special Case Pattern (See MartinFowler.com)if (isNull(address)) {address = Address.INVALID_ADDRESS;}// Do some jiggery-pokery here....return address;}private boolean isNull(Object obj) {return obj == null;}@Autowired@Qualifier("addressDao")void setAddressDao(AddressDao addressDao) {this.addressDao = addressDao;}
}

…通过将他的数据访问对象替换为模拟对象。

在继续之前,最好定义一个模拟对象的确切含义以及它与存根的不同之处。 如果您阅读了我的上一篇博客,您会记得我让Martin Fowler将存根对象定义为:

“存根提供对测试过程中进行的呼叫的固定答复,通常通常根本不响应测试中编程的内容。”

……摘自他的论文《 Mocks Are n't Stubs》 。

那么,模拟对象与存根有何不同? 当您听到人们谈论模拟对象时,他们经常提到他们在嘲笑 行为嘲笑 角色 ,但这意味着什么? 答案在于单元测试和模拟对象共同测试对象的方式。 模拟对象场景如下所示:

  1. 测试中定义了一个模拟对象。
  2. 模拟对象被注入到您的测试对象中
  3. 该测试指定将调用模拟对象上的哪些方法,以及参数和返回值。 这就是所谓的“ 设定期望 ”。
  4. 然后运行测试。
  5. 然后,测试将要求模拟程序验证步骤3中指定的所有方法调用均已正确调用。 如果是,则测试通过。 如果不是,那么测试将失败。

因此,模拟行为或模拟角色实际上意味着检查被测对象是否正确调用了模拟对象上的方法,如果没有,则使测试失败。 因此,您是在断言方法调用的正确性和通过代码的执行路径,而不是在常规单元测试的情况下断言被测试方法的返回值。

尽管有几种专业的模拟框架,但在本例中,我首先决定产生自己的AddressDao模拟,它可以满足上述要求。 毕竟,这有多难?

public class HomeMadeMockDao implements AddressDao {/** The return value for the findAddress method */private Address expectedReturn;/** The expected arg value for the findAddress method */private int expectedId;/** The actual arg value passed in when the test runs */private int actualId;/** used to verify that the findAddress method has been called */private boolean called;/*** Set and expectation: the return value for the findAddress method*/public void setExpectationReturnValue(Address expectedReturn) {this.expectedReturn = expectedReturn;}public void setExpectationInputArg(int expectedId) {this.expectedId = expectedId;}/*** Verify that the expectations have been met*/public void verify() {assertTrue(called);assertEquals("Invalid arg. Expected: " + expectedId + " actual: " + expectedId, expectedId, actualId);}/*** The mock method - this is what we're mocking.* * @see com.captaindebug.address.AddressDao#findAddress(int)*/@Overridepublic Address findAddress(int id) {called = true;actualId = id;return expectedReturn;}
}

支持此模拟的单元测试代码为:

public class MockingAddressServiceWithHomeMadeMockTest {/** The object to test */private AddressService instance;/*** We've written a mock,,,*/private HomeMadeMockDao mockDao;@Beforepublic void setUp() throws Exception {/* Create the object to test and the mock */instance = new AddressService();mockDao = new HomeMadeMockDao();/* Inject the mock dependency */instance.setAddressDao(mockDao);}/*** Test method for* {@link com.captaindebug.address.AddressService#findAddress(int)}.*/@Testpublic void testFindAddressWithEasyMock() {/* Setup the test data - stuff that's specific to this test */final int id = 1;Address expectedAddress = new Address(id, "15 My Street", "My Town", "POSTCODE", "My Country");/* Set the Mock Expectations */mockDao.setExpectationInputArg(id);mockDao.setExpectationReturnValue(expectedAddress);/* Run the test */instance.findAddress(id);/* Verify that the mock's expectations were met */mockDao.verify();}
}

好的,尽管这演示了使用模拟对象执行单元测试所需的步骤,但它相当粗糙且准备就绪,并且非常针对AddressDao / AddressService场景。 为了证明它已经做得更好,下面的示例使用easyMock作为模拟框架。 在这种更专业的情况下,单元测试代码为:

@RunWith(UnitilsJUnit4TestClassRunner.class)
public class MockingAddressServiceWithEasyMockTest {/** The object to test */private AddressService instance;/*** EasyMock creates the mock object*/@Mockprivate AddressDao mockDao;/*** @throws java.lang.Exception*/@Beforepublic void setUp() throws Exception {/* Create the object to test */instance = new AddressService();}/*** Test method for* {@link com.captaindebug.address.AddressService#findAddress(int)}.*/@Testpublic void testFindAddressWithEasyMock() {/* Inject the mock dependency */instance.setAddressDao(mockDao);/* Setup the test data - stuff that's specific to this test */final int id = 1;Address expectedAddress = new Address(id, "15 My Street", "My Town", "POSTCODE", "My Country");/* Set the expectations */expect(mockDao.findAddress(id)).andReturn(expectedAddress);replay();/* Run the test */instance.findAddress(id);/* Verify that the mock's expectations were met */verify();}
}

…我希望您会同意,这比我快速尝试编写模拟游戏更具进步性。

使用模拟对象的主要批评是它们将单元测试代码与生产代码的实现紧密耦合。 这是因为设置期望值的代码紧密跟踪生产代码的执行路径。 这意味着即使该类仍履行其接口协定,后续对生产代码的重构也可能破坏大量测试。 这引起了这样的断言,即模拟测试相当脆弱,并且您将花费不必要的时间修复它们,根据我的经验,尽管我使用了“非严格”模拟,但这种模拟并不关心方法的顺序,尽管我同意期望被称为,在一定程度上减轻了问题。

另一方面,一旦您知道如何使用诸如easyMock之类的框架,就可以非常快速有效地完成将您的对象隔离的单元测试。

在自我批评该示例代码时,我想指出的是,我认为在这种情况下使用模拟对象是过大的,此外,您还可以轻易地认为我将模拟作为存根使用。

几年前,当我第一次遇到easyMock时,我在各处使用了模拟,但是最近我开始更喜欢手动为应用程序边界类(例如DAO)和仅返回数据的对象编写存根。 这是因为基于存根的测试可以说比基于模拟的测试要脆弱得多,尤其是当您需要访问数据时。

为什么要使用模拟? 擅长测试使用“ 告诉不要询问 ”技术编写的应用程序,以验证是否调用了具有无效返回值的方法。

参考: Captain Debug博客上来自JCG合作伙伴 使用Mocks进行单元测试-测试技术5

相关文章 :

  • 测试技巧–不编写测试
  • 端到端测试的滥用–测试技术2
  • 您应该对什么进行单元测试? –测试技术3
  • 常规单元测试和存根–测​​试技术4
  • 为旧版代码创建存根–测试技术6
  • 有关为旧版代码创建存根的更多信息–测试技术7
  • 为什么要编写单元测试–测试技巧8
  • 一些定义–测试技术9
  • 使用FindBugs产生更少的错误代码
  • 在云中开发和测试

翻译自: https://www.javacodegeeks.com/2011/11/unit-testing-using-mocks-testing.html

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

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

相关文章

多线程中的volatile和伪共享

伪共享 false sharing,顾名思义,“伪共享”就是“其实不是共享”。那什么是“共享”?多CPU同时访问同一块内存区域就是“共享”,就会产生冲突,需要控制协议来协调访问。会引起“共享”的最小内存区域大小就是一个cache…

C语言代码规范(一)缩进与换行

一、缩进的空格数为4个。最好配置代码编辑器将TAB键设置为空格替换&#xff0c;避免出现另一个编辑器打开时格式变乱的情况。 例如Notepad设置 KEIL设置 二、“{” 和 “}”各自独占一行。 不规范例子&#xff1a; for(i 0; i < student_num; i) { if((score[i] > 0…

armv7 cortex a系列编程手册_AWTK能为现代GUI编程带来何种改变?

AWTK是一个伸缩性极强的嵌入式图形框架&#xff0c;它的诞生会给GUI编程研发工程师带来哪些改变&#xff1f;AWTK是一个伸缩性极强的嵌入式图形框架&#xff0c;可在Cortex-M3这样低端的单片机上运行&#xff0c;也可以在Cortex-A7/A8/A9等处理器&#xff0c;甚至DSP以及X86处理…

为什么要编写单元测试–测试技巧8

我对最近在“您应该测试什么”上的博客有很多反应&#xff0c;有些人出于各种原因同意我的想法&#xff0c;另一些人则认为建议某些类可能不需要单元测试是非常危险的。 已经处理了什么测试&#xff0c;今天的博客涉及为什么要编写单元测试&#xff0c;而今天的示例代码是基于一…

c++ 多重背包状态转移方程_动态规划入门——详解经典问题零一背包

本文始发于个人公众号&#xff1a;TechFlow&#xff0c;原创不易&#xff0c;求个关注今天是周三算法与数据结构专题的第12篇文章&#xff0c;动态规划之零一背包问题。在之前的文章当中&#xff0c;我们一起探讨了二分、贪心、排序和搜索算法&#xff0c;今天我们来看另一个非…

python定义一个圆_Python-矩形和圆形

原博文 2019-11-11 12:34 − Exercise 15.1. 定义一个叫做Circle 类&#xff0c;类的属性是圆心 (center) 和半径 (radius) , 其中&#xff0c;圆心 (center) 是一个 Point 类&#xff0c;而半径 (radius) 是一个数字。 实例化一个圆心 (center) 为 (150, 100) &#xff0c;半…

STM32F1笔记(一)GPIO输出

GPIO&#xff1a;General Purpose Input Output &#xff08;通用输入/输出&#xff09;。 GPIO最经典应用&#xff1a;LED灯。 先看电路。声明&#xff1a;参考正点原子战舰开发板。 与LED串联的电阻称为限流电阻。 限流电阻计算公式&#xff1a;R(U-LED压降)/20ma。 U为LE…

dataframe转化为array_【Python专栏】12 种高效 Numpy 和 Pandas 函数为你加速分析

来源&#xff1a;机器之心编译&#xff1a;Jamin、杜伟、张倩我们都知道&#xff0c;Numpy 是 Python 环境下的扩展程序库&#xff0c;支持大量的维度数组和矩阵运算&#xff1b;Pandas 也是 Python 环境下的数据操作和分析软件包&#xff0c;以及强大的数据分析库。二者在日常…

具有GlassFish和一致性的高性能JPA –第1部分

您以前听说过连贯性吗&#xff1f; 大概是。 它是那些著名的内存网格解决方案之一&#xff0c;该解决方案承诺了超快的数据访问速度和对经常使用的数据的无限空间。 一些众所周知的竞争对手是Infinispan &#xff0c; Memcached和Terracotta Ehcache 。 它们都很棒&#xff0c;…

boost原理与sklearn源码_机器学习sklearn系列之决策树

一、 Sklearn库 Scikit learn 也简称 sklearn, 自2007年发布以来&#xff0c;scikit-learn已经成为Python重要的机器学习库了。支持包括分类、回归、降维和聚类四大机器学习算法。还包含了特征提取、数据处理和模型评估三大模块。sklearn是Scipy的扩展&#xff0c;建立在NumPy和…

STM32F1笔记(二)GPIO输入

STM32 GPIO输入的经典应用是按键。 先看电路。声明&#xff1a;参考正点原子战舰开发板。 在这里可以看到&#xff0c;KEY_UP按键是高电平有效的&#xff0c;即当按下该按键时&#xff0c;GPIO读到高电平。 KEY0/1/2是低电平有效的&#xff0c;即当按下该按键时&#xff0c;G…

STM32F1笔记(三)UART/USART

UART&#xff1a;Universal Asynchronous Receiver/Transmitter&#xff08;通用异步收/发器&#xff09; USART&#xff1a;Universal Synchronous/Asynchronous Receiver/Transmitter&#xff08;通用同步/异步串行收/发器&#xff09; 从命名即可看出USART就是UART的基础上…

python安装界面翻译_python环境搭建

如果想要运行python需要有解释器和编辑器。 什么是解释器 解释器我们可以把它理解成翻译官&#xff0c;它是将我们写的python代码翻译成计算机能够懂得机器语言。 然后计算机收到解释器的命令来干活&#xff0c;最终再将结果反馈在解释器中。 解释器推荐使用anaconda3 什么是an…

进阶篇-用户界面:4.Android中常用组件

1.下拉菜单 在Web开发中&#xff0c;HTML提供了下拉列表的实现&#xff0c;就是使用<select>元素实现一个下拉列表&#xff0c;在其中每个下拉列表项使用<option>表示即可。这是在Web开发中一个必不可少的交互性组件&#xff0c;而在Android中的对应实现就是Spinne…

http的“无连接”指的是_http协议无状态中的 quot;状态quot; 到底指的是什么?...

引子&#xff1a;最近在好好了解http&#xff0c;发现对介绍http的第一句话【http协议是无状态的&#xff0c;无连接的】就无法理解了&#xff1a;无状态的【状态】到底指的是什么&#xff1f;&#xff01;找了很多资料不仅没有发现有一针见血正面回答这个问题的&#xff0c;而…

个人日志-7.4

姓名 刘鑫 时间 2016.7.4 学习内容 完善需求分析报告。撰写数据库设计说明书。初步安排计划概要设计说明书。调整项目开发计划说明书。 所遇问题 无 解决方案 无 转载于:https://www.cnblogs.com/liuxin13070013/p/5641967.html

STM32F1笔记(五)外部中断EXTI

STM32的每个IO都可以作为外部中断的中断输入口。 STM32F103的中断控制器支持19个外部中断/事件请求。每个中断设有状态为&#xff0c;每个中断/事件都有独立的触发和屏蔽设置。 STM32F103的19个外部中断为&#xff1a; EXTI线0~15&#xff1a;对应外部IO口的输入中断。 EXT…

STM32F1笔记(六)独立看门狗IWDG

STM32F1内置了两个看门狗&#xff0c;独立看门狗IWDG和窗口看门狗WWDG&#xff0c;可以用来检测和解决由软件错误引起的故障。 IWDG最适合应用于那些需要看门狗作为一个在主程序之外&#xff0c;能够完全独立工作&#xff0c;并且对时间精度要求较低的场合。WWDG最适合那些要求…

在JSF 2中对定制验证器进行参数化

在JSF 2中编写自定义验证器并不复杂。 您实现Validator接口&#xff0c;添加FacesValidator批注&#xff0c;并在faces-config.xml中插入Validator声明&#xff0c; 仅此而已 。 一块蛋糕。 但是&#xff0c;让我们考虑以下情形&#xff1a; 您需要自定义日期验证器&#xff0c…

STM32F1笔记(七)WWDG窗口看门狗

窗口看门狗与独立看门狗最大的不同是中断&#xff0c;窗口看门狗拥有一个提前唤醒中断。也就是在快要产生复位的前一段时间&#xff08;T[6:0]0x40&#xff09;来提醒需要进行喂狗&#xff0c;否则将复位。因此当窗口看门狗的计数器值减到0x40的时候&#xff0c;产生中断&#…