使用模拟的单元测试–测试技术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处理…

【转】各种概念POJO、JAVABEAN、DAO、DTO、PO、VO、BO、SSH、EJB

POJO&#xff08;pure old java object&#xff09; 是普通java类&#xff0c;有一些private的参数作为对象的属性&#xff0c;然后针对每一个参数定义get和set方法访问的接口。我看到这个定义&#xff0c;心里就有个疑问了&#xff0c;这个POJO跟JavaBean的定义怎么就这么像&a…

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

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

Git迁移 从SVN到Git

Migrating from SVN to Git 首先我们需要在Stach或者GitHub上新建一个Repository, 拿到它的URL。 接下来参照如下步骤 : At first we should create a new git repository at Stash and get the repository URL, and then follow below steps: 1. 切换到本地git工作目录 chang…

C语言代码规范(二)空格

一、逗号, 之后加空格 printf("error! score[%d] %d\n", i, score[i]); 二、分号; 之后加空格 for(i 0; i < student_num; i) 三、关系运算符<、<、>、>、、! 前后加空格 if( (score[i] > 0) && (score[i] < 100) ) 四、赋值运算符…

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

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

Discuz! 的编码规范

前言 本规范由编程原则组成&#xff0c;融合并提炼了开发人员长时间积累下来的成熟经验&#xff0c;意在帮助形成良好一致的编程风格。适用范围 如无特殊说明&#xff0c;以下规则要求完全适用于Discuz!项目&#xff0c;同时也可大部分适用于COMSENZ旗下其他PHP项目。标准化的重…

C语言代码规范(三)if语句

一、整型变量与0比较 许多人为了一时之便&#xff0c;模仿布尔变量风格写为如下代码 if(value) {... }if(!value) {... } 应当用 或 ! 来与0比较 if(0 value) {... }if(0 ! value) {... } 二、当if内的语句是与常量进行比较时&#xff0c;常量为左值&#xff0c;变量为右…

6月24 面向对象的设计原则-----工厂模式和单列模式

工厂模式&#xff1a; 工厂模式就是专门负责将大量有共同接口的类实例化&#xff0c;而且不必事先知道每次是要实例化哪一个类的模式。它定义一个用于创建对象的接口&#xff0c;由子类决定实例化哪一个类。 工厂模式相当于创建实例对象的new&#xff0c;经常要根据类Class生成…

LeetCode Subsets

原题链接在这里&#xff1a;https://leetcode.com/problems/subsets/ 题目&#xff1a; Given a set of distinct integers, nums, return all possible subsets. Note: Elements in a subset must be in non-descending order.The solution set must not contain duplicate su…

使用ThreadPoolExecutor并行化独立的单线程任务

Java SE 5.0中引入的任务执行框架是简化多线程应用程序的设计和开发的巨大飞跃。 该框架提供了用于管理任务概念&#xff0c;管理线程生命周期及其执行策略的工具。 在此博客文章中&#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;半…

C语言代码规范(四)命名规则

一、宏定义全部字母大写&#xff0c;单词间下划线间隔 #define FLASH_PAGE_SIZE 256 #define FLASH_SECTOR_SIZE (4 * 1024) #define FLASH_BLOCK_SIZE (64 * 1024) #define FLASH_SIZE (16 * 1024 * 1024) 二、const修饰的常量全部字母大写&#xff0c;单词间…

Forbidden You don't have permission to access / on this server PHP

Forbidden You dont have permission to access / on this server PHP 在新安装的谷歌游览器里&#xff0c;打不了PHP网站了&#xff0c;错误显示&#xff1a; Forbidden You dont have permission to access / on this server. 原因还是配置权限问题 解决办法&#xff1a; wa…

Spring 3.1和JPA的持久层

1.概述 本教程显示了如何使用Hibernate作为持久性提供程序使用JPA设置Spring 。 有关使用基于Java的配置和项目的基本Maven pom设置Spring上下文的分步介绍&#xff0c;请参阅本文 。 2. Java的JPA Spring配置 要在Spring项目中使用JPA&#xff0c; 需要设置EntityManager 。…

150928错误认识

1. $arr array(); foreach ($re as $k>$v){  $arr[] $v[updatetime];} $arr的返回结果为&#xff1a; Array ([0] > 2014-09[1] > 2015-04[2] > 2015-09 )$arr array(); foreach ($re as $k>$v){  $arr[$k] $v[updatetime];} $arr的返回结果为&#xff…

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;以及强大的数据分析库。二者在日常…