测试驱动陷阱,第2部分

单元测试中单元的故事

在本文的上半部分 ,您可能会看到一些不好但很流行的测试示例。 但是我不是一个专业评论家(也被称为“巨魔”或“仇恨者”),没有任何建设性的话就抱怨。 多年的TDD教给我的不仅仅是事情会变得多么糟糕。 有许多简单但有效的技巧,可以使您的测试生活变得更加轻松。

想象一下:您有一家小公司中一间小型会议室的预订系统。 由于某些奇怪的原因,它必须处理离线预订。 人们将他们的预订请求发布到某个前端,每周一次,您会获得一个文本文件,其中包含公司的工作时间以及所有的预订(在当天,多长时间,由谁,在什么时间点提交)订购。 您的系统应根据一些业务规则(先到先得,仅在办公室工作时间内提供此类服务)为房间生成日历。

作为分析的一部分,我们提供了明确定义的输入数据和预期结果,并带有示例。 确实,TDD的情况很好。 可悲的是,在现实生活中从来没有发生过这样的事情。

我们的示例测试数据如下所示:

class TestData {static final String INPUT_FIRST_LINE = '0900 1730\n';static final String FIRST_BOOKING    = '2011-03-17 10:17:06 EMP001\n' +'2011-03-21 09:00 2\n';static final String SECOND_BOOKING   = '2011-03-16 12:34:56 EMP002\n' +'2011-03-21 09:00 2\n';static final String THIRD_BOOKING    = '2011-03-16 09:28:23 EMP003\n' +'2011-03-22 14:00 2\n';static final String FOURTH_BOOKING   = '2011-03-17 10:17:06 EMP004\n' +'2011-03-22 16:00 1\n';static final String FIFTH_BOOKING    = '2011-03-15 17:29:12 EMP005\n' +'2011-03-21 16:00 3';static final String INPUT_BOOKING_LINES =FIRST_BOOKING +SECOND_BOOKING +THIRD_BOOKING +FOURTH_BOOKING +FIFTH_BOOKING;static final String CORRECT_INPUT = INPUT_FIRST_LINE + INPUT_BOOKING_LINES;static final String CORRECT_OUTPUT = '2011-03-21\n' +'09:00 11:00 EMP002\n' +'2011-03-22\n' +'14:00 16:00 EMP003\n' +'16:00 17:00 EMP004\n' +'';
}

现在,我们从一个积极的测试开始:

BookingCalendarGenerator bookingCalendarGenerator =  new BookingCalendarGenerator();@Test
public void shouldPrepareBookingCalendar() {//whenString calendar = bookingCalendarGenerator.generate(TestData.CORRECT_INPUT);//thenassertEquals(TestData.CORRECT_OUTPUT, calendar);
}

看起来我们已经使用“生成”方法设计了BookingCalendarGenerator。 很公平。 让我们添加更多测试。 测试业务规则。 我们得到这样的东西:

@Testpublic void noPartOfMeetingMayFallOutsideOfficeHours() {//givenString tooEarlyBooking = '2011-03-16 12:34:56 EMP002\n' +'2011-03-21 06:00 2\n';String tooLateBooking = '2011-03-16 12:34:56 EMP002\n' +'2011-03-21 20:00 2\n';//whenString calendar = bookingCalendarGenerator.generate(TestData.INPUT_FIRST_LINE + tooEarlyBooking + tooLateBooking);//thenassertTrue(calendar.isEmpty());}@Testpublic void meetingsMayNotOverlap() {//givenString firstMeeting = '2011-03-10 12:34:56 EMP002\n' +'2011-03-21 16:00 1\n';String secondMeeting = '2011-03-16 12:34:56 EMP002\n' +'2011-03-21 15:00 2\n';//whenString calendar = bookingCalendarGenerator.generate(TestData.INPUT_FIRST_LINE + firstMeeting + secondMeeting);//thenassertEquals('2011-03-21\n' +'16:00 17:00 EMP002\n', calendar);}@Testpublic void bookingsMustBeProcessedInSubmitOrder() {//givenString firstMeeting = '2011-03-17 12:34:56 EMP002\n' +'2011-03-21 16:00 1\n';String secondMeeting = '2011-03-16 12:34:56 EMP002\n' +'2011-03-21 15:00 2\n';//whenString calendar = bookingCalendarGenerator.generate(TestData.INPUT_FIRST_LINE + firstMeeting + secondMeeting);//thenassertEquals('2011-03-21\n15:00 17:00 EMP002\n', calendar);}@Testpublic void orderingOfBookingSubmissionShouldNotAffectOutcome() {//givenList<String> shuffledBookings = newArrayList(TestData.FIRST_BOOKING, TestData.SECOND_BOOKING,TestData.THIRD_BOOKING, TestData.FOURTH_BOOKING, TestData.FIFTH_BOOKING);shuffle(shuffledBookings);String inputBookingLines = Joiner.on('\n').join(shuffledBookings);//whenString calendar = bookingCalendarGenerator.generate(TestData.INPUT_FIRST_LINE + inputBookingLines);//thenassertEquals(TestData.CORRECT_OUTPUT, calendar);}

仅此而已。 但是,如果我们得到一些垃圾作为输入怎么办。 还是如果我们得到一个空字符串? 让我们为此设计:

@Test(expected = IllegalArgumentException.class)public void rubbishInputDataShouldEndWithException() {//whenString calendar = bookingCalendarGenerator.generate('rubbish');//then exception is thrown}@Test(expected = IllegalArgumentException.class)public void emptyInputDataShouldEndWithException() {//whenString calendar = bookingCalendarGenerator.generate('');//then exception is thrown}

IllegalArgumentException很公平。 我们不需要再花哨的方式处理它。 我们已经完成了。 最后,让我们在测试下编写该类:BookingCalendarGenerator。

因此,我们做到了。 结果表明,对于一个方法来说,整个过程有点大。 因此,我们使用了“提取方法”模式的强大功能。 我们将代码片段分为不同的方法。 我们将进行操作的方法和数据分组为类。 我们使用面向对象编程的功能,使用单一职责原理,使用合成(确切地说是分解),最后得到一个像这样的包:

我们有一个公共类,和几个包作用域类。 这些包范围类显然属于公共类。 这是一个清晰的类图:

那些不是愚蠢的数据对象。 这些是成熟的课程。 具有行为,责任感和封装感。 这是我们测试驱动者可能想到的一件事:我们没有针对这些类的测试。 我们只有公共班级。 不好,对吧? 没有测试一定是不好的。 很坏。 对?

错误。

我们有测试。 我们启动了代码覆盖率工具,然后看到:100%方法和类。 95%的线。 不错(在下一篇文章中,我将达到不确定性的5%)。

但是我们只有一个单元测试类。 这样好吗

好吧,让我强调一下,指出答案:

这是一个UNIT测试。 有理由将其称为UNIT测试!

该单元不必是一个单一的类。 该单元不必是单个包装。 由您决定单位。 这是一个通用名称,因为您的理智和常识应该告诉您停止的地方。

因此,我们有六个班级作为一个单元,有什么大不了的? 除了其余部分,有人是否要使用其中一个类呢? 他不会对此进行测试,对吗?

错误。 除了在测试中实际调用的类之外​​,这些类都是包范围的。 这个包裹范围的事情告诉您:“退后一步。 不要碰我,我属于这个包裹。 不要试图单独使用我,我是设计在这里!”。

因此,是的,如果程序员将其中之一删除或公开,则他可能会知道,所有保证都将失效。 写你自己的测试,伙计。

我被问到是否有人要向其中一个类添加某些行为呢? 他怎么会知道他没有破坏什么?

好吧,他将从测试开始,对吗? 是TDD,对不对? 如果您有需求变更,则可以将此变更编码为测试,然后,直到那时,您才开始弄乱代码。 因此,您是安全的。

我看到人们盲目地写每堂课的测试,却没有思考,这让我哭了。 我最近做了很多配对编程,您知道我发现了什么吗? Java程序员通常不使用package-scope。 Java程序员通常不知道,受保护的意思是:对我来说,我的所有后代和每个人都在同一软件包中。 没错,受保护不仅仅是包范围,更不是一点点。 因此,如果Java程序员不知道包范围实际上是什么,并且与Groovy相反,那是默认的,那么他们如何理解单元是什么?

我能得到多高?

现在,有一个有趣的想法:如果我们可以对一个包进行单个测试,那么我们可以对一个包树进行单个测试。 你知道,像这样:

我们都知道Java包不是真正的树状包,唯一具有目录结构的是按照非常古老的约定,而且我们知道目录结构仅用于解决名称冲突问题,尽管如此,我们还是倾向于使用包,就像name.after.the.dot有意义一样。 就像我们可以将一个包裹隐藏在另一个包裹中一样。 或与他们建立千层面的烤宽面条。

那么可以为一个树形树使用一个测试类吗?

是的。

但是,如果是这样,到哪里结束? 我们可以从包树一直到应用程序的入口点吗? 那些……也许是集成测试或功能测试。 我们能做到吗? 那会好吗?

答案是:可以。 在一个完美的世界中,那会很好。 在我们那肮脏的,悬挂在刀刃上的世界里,那简直是疯了。 为什么? 因为功能性的端到端测试很慢。 太慢了。 太慢了,以至于您想将它们扔掉,然后到某个地方,而不必总是等待某些东西。 一个充满创造力,持续反馈和闪电般快速安全的地方。

您将返回单元测试。

甚至还有更多原因。 一种是,很难对应用程序的所有流进行端到端测试。 您可能应该对所有主要流程都执行此操作,但是对于错误,连接不良以及所有可能在某一点或另一点抛出的棘手逻辑部分呢? 不,有时候像这样为集成测试设置环境会太困难了,所以最终还是要用单元测试来测试它。

第二个原因是,尽管功能测试并未在代码上浇注具体的内容,但不会通过在测试用例中重复执行算法来抑制您的创造力,但是它们也没有提供重构的安全性。 当您拥有一个带有单个公共类的程序包时,很明显有人可以安全地做什么,而他不能做什么。 当您将某些内容包含在库或插件中时,它仍然很明显。 但是,如果您有成千上万个公共类,并且要实现一个新功能,则可能要使用其中一些,并且您想知道它们很好。

因此,不,在我们的世界中,仅进行功能测试是没有意义的。 抱歉。 但是为每个类创建测试也没有意义。 出于某种原因,它称为UNIT测试。 用那个

祝您编程愉快,别忘了分享!

参考:“ 测试驱动陷阱”,来自我们JCG合作伙伴 Jakub Nabrdalik的第2部分 ,在Solid Craft博客上。


翻译自: https://www.javacodegeeks.com/2012/09/test-driven-traps-part-2.html

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

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

相关文章

java 代码 设置环境变量_Java 配置环境变量教程

【声明】欢迎转载&#xff0c;但请保留文章原始出处→_→【正文】1、安装JDK开发环境开始安装JDK&#xff1a;修改安装目录如下&#xff1a;确定之后&#xff0c;单击“下一步”。注&#xff1a;当提示安装JRE时&#xff0c;可以选择不要安装。2、配置环境变量&#xff1a;对于…

组合数据类型练习,英文词频统计实例上(2017.9.22)

字典实例&#xff1a;建立学生学号成绩字典&#xff0c;做增删改查遍历操作。 sno[33号,34号,35号,36号] grade[100,90,80,120] d{33号:100,34号:90,35号:80,36号:120} print(d) print(每个学号对应分数:,d.items()) print(弹出35号的分数:,d.pop(35号)) print(获取学号:,d.key…

java 代码中设置 临时 环境变量

System.setProperty("hadoop.home.dir", "D:\\software\\software_install\\dev_install\\hadoop-2.4.1"); 转载于:https://www.cnblogs.com/zychengzhiit1/p/6662376.html

什么是快速开发框架

什么是快速开发框架 前言 做为一个程序员&#xff0c;在开发的过程中会发现&#xff0c;有框架同无框架&#xff0c;做起事来是完全不同的概念&#xff0c;关系到开发的效率、程序的健壮、性能、团队协作、后续功能维护、扩展......等方方面面的事情。很多朋友在学习搭建自己…

java中的math.abs_Java.math.BigDecimal.abs()方法

全屏Java.math.BigDecimal.abs()方法java.math.BigDecimal.abs()返回一个BigDecimal&#xff0c;其值是此BigDecimal的绝对值&#xff0c;其标度是this.scale()。声明以下是java.math.BigDecimal.abs()方法的声明public BigDecimal abs()参数NA返回值此方法返回的名为value&…

我需要多少内存

什么是保留堆&#xff1f; 我需要多少内存&#xff1f; 在构建解决方案&#xff0c;创建数据结构或选择算法时&#xff0c;您可能会问自己&#xff08;或其他人&#xff09;这个问题。 如果此图包含1,000,000条边并且我使用HashMap进行存储&#xff0c;此图是否适合我的3G堆&am…

C语言程序设计预报作业

1阅读邹欣老师的博客--师生关系,针对文中的几种师生关系谈谈你的看法&#xff0c;你期望的师生关系是什么样的&#xff1f; 答&#xff1a;我认为文中的师生关系都存在一些缺陷&#xff0c;第一种师生关系是建立在病态关系上的&#xff0c;学生不是植物自然有自己的思想。所以我…

浅谈23种设计模式

浅谈23种设计模式 类之间的关联关系&#xff1a;在使用Java、C#和C等编程语言实现关联关系时&#xff0c;通常将一个类作为另一个类的属性。   (1)双向关联&#xff0c;两个类互相为各自的属性&#xff0c;比如顾客类Customer和商品类Product&#xff0c;顾客拥有商品&#x…

网页布局基础

1、盒子模型的第一层到第五层&#xff1a; border、padding content、background-image、background-color、margin 2、清除浮动。对受到浮动影响的标签作以下操作&#xff1a; 1、clear: both; 2、clear: right; clear: left; 3、设置宽度width: 100%(或者固定宽度) overflow…

mysql与串口通信_虚拟机串口与主机串口通信·小程序(下)

上次说到的&#xff0c;不能做到实时通信。那么开两个进程就可以了&#xff0c;一个用来监听是否有消息传来&#xff0c;一个用来等待用户输入。那么&#xff0c;先来复习一下进程的相关概念。进程结构linux中进程包含PCB(进程控制块)、程序以及程序所操纵的数据结构集&#xf…

浅谈我所见的CSS命名风格

在两年工作中&#xff0c;总结一下我所见的css命名风格。 1.单一class命名 .header {width: 500px; } .item {text-indent: 20%; } 优点&#xff1a;简单&#xff0c;渲染效率高。 缺点&#xff1a;零散&#xff0c;没有模块化。 2. 后代选择器class命名 .header .item a {font…

Java规范请求中的数字

你们都了解Java社区流程 &#xff08;JCP&#xff09;&#xff0c;不是吗&#xff1f; JCP是为Java技术开发标准技术规范的机制。 任何人都可以注册该站点并参与对Java规范请求&#xff08;JSR&#xff09;的审查和提供反馈&#xff0c;并且任何人都可以注册成为JCP成员&#x…

ORACLE MOS 翻译

http://blog.csdn.net/msdnchina/article/details/53174196转载于:https://www.cnblogs.com/zengkefu/p/6665950.html

自从我这样撸代码以后,公司网页的浏览量提高了107%!

欢迎大家前往腾讯云 社区&#xff0c;获取更多腾讯海量技术实践干货哦~ 本文由腾讯IVWEB团队发表于云 社区专栏 作者&#xff1a;yangchunwen HTTP协议是前端性能乃至安全中一个非常重要的话题&#xff0c;最近在看《web性能权威指南(High Performance Browser Networking)》&a…

python数列分段_按范围分段的Python数组

首先&#xff0c;定义你的“极”数第二&#xff0c;根据这些“极”数生成间隔第三&#xff0c;定义尽可能多的列表。在然后&#xff0c;对于每个间隔&#xff0c;扫描列表并在相关列表中添加属于该间隔的项代码&#xff1a;source [1, 4, 7, 9, 2, 10, 5, 8]poles (0,3,6,25)…

51nod 1278 相离的圆

基准时间限制&#xff1a;1 秒 空间限制&#xff1a;131072 KB 分值: 10 难度&#xff1a;2级算法题 平面上有N个圆&#xff0c;他们的圆心都在X轴上&#xff0c;给出所有圆的圆心和半径&#xff0c;求有多少对圆是相离的。例如&#xff1a;4个圆分别位于1, 2, 3, 4的位置&…

让我们将包变成模块系统!

使用构建系统将许多项目分为模块/子项目&#xff08; Maven &#xff0c; Gradle &#xff0c; SBT …&#xff09;&#xff1b; 编写模块化代码通常是一件好事。 将代码分为构建模块主要用于&#xff1a; 隔离代码部分&#xff08;减少耦合&#xff09; api / impl拆分 仅将…

R语言日期的表示和运算(详细总结)

1、取出当前日期 Sys.Date() [1] "2014-10-29" date() #注意&#xff1a;这种方法返回的是字符串类型 [1] "Wed Oct 29 20:36:07 2014" 2、在R中日期实际是double类型&#xff0c;是从1970年1月1日以来的天数 typeof(Sys.Date()) [1] "double" …

html高度塌陷问题解决

高度塌陷的问题&#xff1a; 当开启元素的BFC以后&#xff0c;元素将会有如下的特性 1 父元素的垂直外边距不会和子元素重叠 开启BFC的元素不会被浮动元素所覆盖 开启BFC的元素可以包含浮动的子元素 如何开启元素的BFC 设置元素浮动 设置元素绝对定位 …

java空格键_Java KeyPressed-如果其他键也太旧,则无法检测是否按下了空格键

如标题所示&#xff0c;在我的Java游戏中&#xff0c;无法检测是否同时按下空格键和其他键。例如&#xff0c;空格键是射击键&#xff0c;而箭头键则使玩家移动。如果我按下向上箭头键&#xff0c;向左箭头键和空格键&#xff0c;那么它应该向左上方发射子弹。但是&#xff0c;…