在测试中使用匹配器

我们被迫在测试代码中写太多断言行的日子已经一去不复返了。 镇上有一个新的警长:assertThat和他的代理人:匹配者。 好吧,这不是什么新东西,但是无论如何,我想向您介绍匹配器的使用方式,然后对匹配器概念进行扩展,我发现这对于为代码开发单元测试非常有用。

首先,我将介绍匹配器的基本用法。 当然,您可以直接从其作者那里完整地了解hamcrest匹配器功能:
https://code.google.com/p/hamcrest/wiki/Tutorial 。

基本上,匹配器是定义两个对象何时匹配的对象。 通常,第一个问题是您为什么不使用等于? 好吧,有时您不想在它们的所有字段上都匹配两个对象,而只是在其中的某些字段上匹配,如果您使用旧代码,则会发现equals实现不存在或不符合您的预期。 另一个原因是使用assertThat为您提供了一种更一致的“断言”方法,并且可以说是更具可读性的代码。 因此,例如,而不是编写:

int expected, actual;
assertEquals(expected, actual);

你会写

assertThat(expected, is(actual));

其中“ is”是静态导入的org.hamcrest.core.Is.is

并没有太大的区别……。 但是Hamcrest为您提供了许多非常有用的匹配器:

  • 对于数组和映射:hasItem,hasKey,hasValue
  • 数字:closeTo –一种指定相等性的方法,其边距误差大于,大于,小于…
  • 对象:nullValue,sameInstance

现在我们正在取得进步……Hamcrest匹配器的功能仍然是您可以为对象编写自己的匹配器。 您只需要扩展BaseMatcher <T>类。 这是一个简单的自定义匹配器的示例:

public class OrderMatcher extends BaseMatcher<Order> {private final Order expected;private final StringBuilder errors = new StringBuilder();private OrderMatcher(Order expected) {this.expected = expected;}@Overridepublic boolean matches(Object item) {if (!(item instanceof Order)) {errors.append("received item is not of Order type");return false;}Order actual = (Order) item;if (actual.getQuantity() != (expected.getQuantity())) {errors.append("received item had quantity ").append(actual.getQuantity()).append(". Expected ").append(expected.getQuantity());return false;}return true;}@Overridepublic void describeTo(Description description) {description.appendText(errors.toString());}@Factorypublic static OrderMatcher isOrder(Order expected) {return new OrderMatcher(expected);}
}

与旧的断言方法相比,这是一个全新的联盟。

因此,这简而言之就是Hamcrest的匹配器的用法。

但是,当我开始在现实生活中使用它时,尤其是在使用遗留代码时,我意识到故事还有很多。 这是使用匹配器时遇到的一些问题:

  1. 匹配器的结构可能非常重复且无聊。 我需要一种将DRY原理应用于匹配器代码的方法。
  2. 我需要一种统一的方式来访问匹配器。 默认情况下,框架应选择正确的匹配器。
  3. 我需要比较引用了另一个对象的对象,这些对象应该已经与匹配器进行了比较(对象引用可以根据需要进行深入处理)
  4. 我需要使用匹配器检查对象集合,而无需迭代该集合(也可以使用数组匹配器……但我想要更多的J)
  5. 我需要一个更灵活的匹配器。 例如,对于同一对象,我需要检查一组字段,但在另一种情况下,则需要检查另一组。 开箱即用的解决方案是为每种情况配备一个匹配器。 不喜欢那样

我使用了一些约定的匹配器层次结构克服了这些问题,并且知道哪些匹配器要应用以及比较或忽略哪个字段。 此层次结构的根是扩展BaseMatcher <T>的RootMatcher <T>。

为了处理#1问题(重复代码),RootMatcher类包含所有匹配器的通用代码,例如用于检查实际值是否为null或与预期对象具有相同类型,甚至是它们是否相同的方法。同一实例:

public boolean checkIdentityType(Object received) {if (received == expected) {return true;}if (received == null || expected == null) {return false;}if (!checkType(received)){return false;}return true;}private boolean checkType(Object received) {if (checkType && !getClass(received).equals(getClass(expected))) {error.append("Expected ").append(expected.getClass()).append(" Received : ").append(received.getClass());return false;}return true;}

这将简化匹配器的编写方式,我不必考虑null或恒等角情况; 所有这些都在根类中解决了。

预期的对象和错误也位于根类中:

public abstract class RootMatcher extends BaseMatcher {protected T expected;protected StringBuilder error = new StringBuilder("[Matcher : " + this.getClass().getName() + "] ");

这样,您可以在扩展RootMatcher之后立即进入match方法的实现,而对于错误,只需将消息放入StringBuilder中即可。 RootMatcher将处理将它们发送到JUnit框架以呈现给用户的情况。

对于问题2(自动查找匹配项),解决方案采用其工厂方法:

@Factorypublic static  Matcher is(Object expected) {return getMatcher(expected, true);}public static  RootMatcher getMatcher(Object expected, boolean checkType) {try {Class matcherClass = Class.forName(expected.getClass().getName() + "Matcher");Constructor constructor = matcherClass.getConstructor(expected.getClass());return (RootMatcher) constructor.newInstance(expected);} catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {}return (RootMatcher) new EqualMatcher(expected);}

如您所见,factory方法尝试使用两种约定来找出应该返回哪个匹配器

  1. 对象的匹配器具有对象名称+字符串Matcher
  2. 匹配器与要匹配的对象位于同一包中(建议位于同一包中,但在测试目录中)

使用此策略,我成功使用了一个匹配器:RootMatcher.is,它将为我提供所需的确切匹配器

为了解决对象关系(第3个问题)的递归性质,在检查对象字段时,我使用了RootManager中的方法来检查将使用匹配器的相等性:

public boolean checkEquality(Object expected, Object received) {String result = checkEqualityAndReturnError(expected, received);return result == null || result.trim().isEmpty();}public String checkEqualityAndReturnError(Object expected, Object received) {if (isIgnoreObject(expected)) {return null;}if (expected == null && received == null) {return null;}if (expected == null || received == null) {return "Expected or received is null and the other is not: expected " + expected + " received " + received;}RootMatcher matcher = getMatcher(expected);boolean result = matcher.matches(received);if (result) {return null;} else {StringBuilder sb = new StringBuilder();matcher.describeTo(sb);return sb.toString();}}

但是集合(问题4)呢? 为了解决这个问题,您要做的就是为扩展RootMatcher的集合实现匹配器。

因此,唯一剩下的问题是#5:使匹配器更加灵活,能够告诉匹配器它应该忽略哪个字段以及应该考虑哪个字段。 为此,我介绍了“ ignoreObject”的概念。 当匹配器在模板(期望的对象)中找到对其的引用时,该对象将忽略该对象。 它是如何工作的? 首先,在RootMatcher中,我提供了用于返回任何Java类型的ignore对象的方法:

private final static Map ignorable = new HashMap();static {ignorable.put(String.class, "%%%%IGNORE_ME%%%%");ignorable.put(Integer.class, new Integer(Integer.MAX_VALUE - 1));ignorable.put(Long.class, new Long(Long.MAX_VALUE - 1));ignorable.put(Float.class, new Float(Float.MAX_VALUE - 1));}/*** we will ignore mock objects in matchers*/private boolean isIgnoreObject(Object object) {if (object == null) {return false;}Object ignObject = ignorable.get(object.getClass());if (ignObject != null) {return ignObject.equals(object);}return Mockito.mockingDetails(object).isMock();}@SuppressWarnings("unchecked")public static  M getIgnoreObject(Class clazz) {Object obj = ignorable.get(clazz);if (obj != null) {return (M) obj;}return (M) Mockito.mock(clazz);}@SuppressWarnings("unchecked")public static  M getIgnoreObject(Object obj) {return (M) getIgnoreObject(obj.getClass());}

如您所见,被忽略的对象将是被模拟的对象。 但是对于无法模拟的类(最终类),我提供了一些不太可能出现的任意固定值(可以对J进行改进)。 为此,开发人员必须使用RootMatcher中提供的equals方法:checkEqualityAndReturnError,它将检查是否忽略了对象。 使用我去年提出的这种策略和构建器模式( http://www.javaadvent.com/2012/12/using-builder-pattern-in-junit-tests.html ),我可以轻松地对复杂的结构做出断言宾语:

import static […]RootMatcher.is;
Order expected = OrderBuilder.anOrder().withQuantity(2).withTimestamp(RootManager.getIgnoredObject(Long.class)).withDescription(“specific description”).build()
assertThat(order, is(expected);

如您所见,我可以轻松地指定应忽略时间戳记,这使我可以将同一匹配器与要验证的一组完全不同的字段一起使用。

确实,此策略需要进行大量准备,使所有的构建者和匹配者成为可能。 但是,如果我们要拥有经过测试的代码,并且要使测试成为主要关注应涵盖的测试流程的工作,那么我们需要这样的基础和这些工具来帮助我们轻松地建立前提条件和建立我们的预期状态。

当然,可以使用注释来改进实现,但是核心概念仍然存在。

我希望本文能帮助您改善测试风格,如果有足够的兴趣,我会尽力将完整的代码放在公共存储库中。

谢谢。

参考:来自Java日历日历博客的JCG合作伙伴 Stefan Bulzan的“ 在测试中使用匹配器” 。

翻译自: https://www.javacodegeeks.com/2013/12/using-matchers-in-tests.html

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

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

相关文章

二、先在SD卡上启动U-boot,再烧写新的U-boot进Nandflash

1. 制作SD卡 先准备一张2G的SD卡&#xff08;不能用8G的&#xff0c;2G的卡和8G的卡协议不一样&#xff09;&#xff0c;和烧写SD卡的工具write_sd以及需要烧写到SD卡中的u-boot-movi.bin。将SD卡格式化后连接到Ubuntu虚拟机中&#xff0c;注意在SD卡需要插入到读卡器中&#x…

泉州经贸职业技术学院计算机系,部门简介-泉州经贸职业技术学院网络电教中心...

泉州经贸职业技术学院网络电教中心成立于2004年&#xff0c;是学院教学辅助部门&#xff0c;为学院教学、行政和后勤等工作提供计算机及网络技术支持。网络电教中心是学院信息化建设职能部门&#xff0c;主要负责各类计算机公共机房、多媒体教室的运行与维护&#xff0c;学院信…

编译型语言,解释型语言,脚本语言

编译型语言 编译型语言在执行之前就将代码编译成了机器语言&#xff0c;当执行的时候就直接运行机器语言&#xff0c;就可以了&#xff0c;如java&#xff0c;编译之后产生.class文件&#xff0c;然后JVM直接执行编译产生的文件就可以了&#xff0c;只要源代码没有发生改变&…

pandas 生成html表格_Pandas 读写html

Pandas 读写html,pandas提供read_html(),to_html()两个函数用于读写html格式的文件。这两个函数非常有用,把DataFrame等复杂的数据结构转换成HTML表格很简单,无需编写一长串HTML代码就能实现。pandas这方面的能力很强大,如果你从事web开发,这个功能将给你带来很多便捷。 读…

java comparator_Java基础之String漫谈(二)

Java-String1. 导读上期分享了本人关于String四个问题, 本期我们继续探讨String中的两个问题:.1 String既然已经实现了Comparable接口, 为什么还要提供内部类----CaseInsensitiveComparator;.2 使用 "" 拼接String究竟干了什么? 为什么在循环中不让使用""…

vue给input file绑定函数获取当前上传的对象

HTML<input type"file" change"tirggerFile($event)"> JS(vue-methods)tirggerFile : function (event) {var file event.target.files; // (利用console.log输出看结构就知道如何处理档案资料)// do something... } 如果直接在绑定的函数中传入thi…

掌握Java字节码

嘿! Happy Advent&#xff1a;D我是ZeroTurnaround的技术布道者Simon Maple&#xff08; sjmaple&#xff09; 。 您知道&#xff0c; JRebel伙计们&#xff01; 由于编写了类似于JRebel的产品&#xff0c;该产品与字节码进行交互的结果比您想像的更多&#xff0c;因此&#xf…

elasticsearch——海量文档高性能索引系统

elasticsearch elasticsearch是一个高性能高扩展性的索引系统&#xff0c;底层基于apache lucene。 可结合kibana工具进行可视化。 概念&#xff1a; index 索引: 类似SQL中的一张表&#xff0c;索引名必须是全小写单词。type&#xff08;索引类型&#xff09;&#xff1a;设计…

全国计算机c二级编程题,全国计算机二级C上机 编程题.doc

全国计算机二级C上机 编程题.doc全国计算机二级C上机 编程题全国计算机等级考试C语言――编程题1&#xff0e;m个人的成绩存放在score数组中,请编写函数fun,它的功能是:将低于平均分的人数作为函数值返回,将低于平均分的分数放在below所指的数组中。例如,当score数组中的数据为…

.Net架构篇:思考如何设计一款实用的分布式监控系统?

前言无论从最早期的unix操作系统&#xff0c;还是曾经大行其道的单体式应用&#xff0c;还是现在日益流行的微服务架构&#xff0c;始终都离不开监控的身影。如windows的任务管理器&#xff0c;linux的top命令&#xff0c;都可以看作是监控的面板。再联系起现实生活&#xff0c…

敲代码括号技巧_理解代码块概念,养成良好编程习惯 | 亲子课堂 第 3 课

亲子课堂关卡解析 / 英语教学 / 编程讲解 做亲子编程教育的好帮手&#xff01; 每周二、四定期更新 地牢面向真正0编程基础的孩子们&#xff0c;关卡被设计成迷宫的形式&#xff0c;引导孩子们使用编程思维解决问题。以循序渐进的方式&#xff0c;让大家理解掌握几个Pyth…

在struts2中push方法的使用_电脑使用中怎么截屏的几种方法

电脑在日常工作中经常需要用到截屏的操作&#xff0c;为了截取画面提供证明或者说明&#xff0c;像我就经常需要用到&#xff0c;当然我在写文章的时候更是需要用到&#xff0c;来配合文字的描述&#xff0c;使大家能更直观更容易的去操作&#xff0c;以达到快速解决电脑问题的…

vue父组件向子组件动态传值的两种方法

在一些项目需求中需要父组件向子组件动态传值&#xff0c;比如我这里的需求是&#xff0c;父组件动态通过axios获取返回的图片url数组然后传给子组件&#xff0c;上传图片的子组件拿到该数组后进行遍历并展示图片&#xff0c;因为有时候获取到的会是空&#xff0c;所以这里要考…

什么是Spring Data?

&#xff08;这与“学生计划”有关&#xff0c;稍后我将重新讨论该主题。&#xff09; Spring Data在最近的几次采访中获得通过。 什么是Spring Data &#xff1f; 为了回答这个问题&#xff0c;让我们考虑持久性的标准方法–所有访问都是通过数据访问对象 &#xff08;DAO&a…

卸载WPS后,原office出现各种问题,报错,图标混乱

1.运行环境win7专业版64位操作系统&#xff0c;之前电脑上装了WPS和office2013&#xff0c;后来卸载了WPS&#xff0c;导致office图标显示不正常&#xff08;因为WPS与office有很多冲突的地方&#xff0c;卸载的时候会影响到注册表&#xff0c;导致office的注册表损坏&#xff…

计算机一级应用于段落还是文字,计算机一级复习资料

出国留学网小编们精心为广大考生准备了“2017年计算机一级PS基础知识点”&#xff0c;各位同学赶快学起来吧&#xff0c;做好万全准备&#xff0c;祝各位同学考试顺利通过。更多相关资讯请持续关注出国留学网。AdobePhotoshop 是目前最流行的平面设计软件之一。可以说&#xff…

C#中的CultureInfo类

CultureInfo类位于System.Globalization命名空间内&#xff0c;这个类和命名空间许多人都不是很熟悉&#xff0c;实际我们在写程序写都经常间接性的接触这个类&#xff0c;当进行数字&#xff0c;日期时间&#xff0c;字符串匹配时&#xff0c;都会进行CultureInfo的操作&#…

clistctrl控件最后插入在后面_老板让我把图片放到Excel表格中,批量插入效率高...

私信回复关键词【福利】&#xff0c;获取丰富办公资源&#xff01;助你高效办公早下班&#xff01;大家好&#xff0c;我是懂点 Excel 的小E~初入「江湖」&#xff0c;还请大家多多关照&#xff01;今天我们来学学 Excel 图片的 6 个小技巧&#xff0c;满满都是干货&#xff0c…

python天气预报的功能介绍_python实现智能语音天气预报

本系统主要包括四个函数&#xff1a; 1、获取天气数据 1、输入要查询天气的城市 2、利用urllib模块向中华万年历天气api接口请求天气数据 3、利用gzip解压获取到的数据&#xff0c;并编码utf-8 4、利用json转化成python识别的数据&#xff0c;返回为天气预报数据复杂形式的字典…

vue获取DOM元素并设置属性

这里我想到了2个方法&#xff1a; 方法一&#xff1a; 直接给相应的元素加id,然后再document.getElementById("id");获取&#xff0c;然后设置相应属性或样式 方法二&#xff1a; 使用ref,给相应的元素加ref“name” 然后再this.$refs.name获取到该元素 注意&…