简而言之,JUnit:测试结构

尽管存在关于JUnit测试的书籍和文章,但我仍然经常遇到程序员,他们至多对这个工具及其正确用法都不甚了解。 因此,我想到了编写多部分教程的想法,从我的角度解释了要点。

也许在这个小型系列中采用的动手方法可能适合使一两个额外的开发人员对单元测试感兴趣,这将使工作值得。

上次我介绍了测试的基本知识–测试的编写,执行和评估方式。 在这样做的同时,我概述了测试不仅仅是一个简单的验证机,而且还可以用作一种低级规范。 因此,应该以人们可能想到的最高编码标准来开发它。

这篇文章将继续本教程的示例,并使用Meszaros在xUnit Test Patterns [MES]中定义的命名法,得出表征良好编写的单元测试的通用结构。

测试的四个阶段


整洁的房子,整洁的头脑
老格言

本教程的示例是关于编写一个简单的数字范围计数器,该计数器从给定值开始提供一定数量的连续整数。 从快乐的路径开始,最后一个帖子的结果是一个测试,该测试已验证, NumberRangeCounter在后续调用next方法时返回连续数字:

@Testpublic void subsequentNumber() {    NumberRangeCounter counter = new NumberRangeCounter();int first = counter.next();int second = counter.next();assertEquals( first + 1, second );}

请注意,本章将坚持使用JUnit内置功能进行验证。 我将在另一篇文章中介绍特定匹配器库( Hamcrest , AssertJ )的优缺点。

细心的读者可能已经注意到,我使用空行将测试分为不同的部分,并且可能想知道为什么。 为了回答这个问题,让我们更仔细地研究三个部分:

  1. 第一个创建要测试的对象的实例,称为SUT被测系统)。 通常,本节在进行任何与测试相关的活动之前会确定SUT的状态。 由于此状态构成了定义良好的测试输入,因此也称为测试夹具
  2. 建立固定装置后,就该调用SUT的那些方法了, 这些方法代表测试要验证的某种行为。 通常,这只是一个方法,结果存储在局部变量中。
  3. 测试的最后一部分负责验证是否已获得给定行为的预期结果。 尽管有一种思想流传着“每次测试一个声明”的策略,但是我更喜欢“ 每次测试一个概念”的想法,这意味着本节不仅仅局限于一个断言,因为它恰好在示例中[MAR1]。

    这种测试结构非常普遍,并已被多位作者描述。 它被标记为排列,执行,声明 [KAC] –或构建,操作,检查 [MAR2] –模式。 但是,对于本教程,我想精确一点并坚持使用Meszaros的[MES]这四个阶段,分别是 设置(1),练习(2),验证(3)拆卸(4)

  4. 拆卸阶段是为了在长期存在的情况下清理灯具。 持久表示夹具或夹具的一部分将在测试结束后继续存在,并且可能对其后继产品的结果产生不良影响。

普通单元测试很少使用持久性夹具​​,因此拆卸阶段(如我们的示例所示)通常被省略。 而且由于它与规范角度完全不相关,因此无论如何我们都希望将其排除在测试方法之外。 一分钟内将介绍如何实现此目的。

由于这篇文章的范围,我避免了单元测试的精确定义。 但是,我坚持Tomek Kaczanowski在使用JUnit和Mockito进行实用单元测试中描述的三种类型的开发人员测试 ,可以概括为:

  • 单元测试可确保您的代码正常运行,并且必须经常运行,因此运行速度非常快。 基本上,这就是本教程的全部内容。
  • 集成测试关注于不同模块的正确集成,包括开发人员无法控制的代码。 这通常需要一些资源(例如数据库,文件系统),因此测试运行速度较慢。
  • 端到端测试从客户端的角度验证您的代码是否有效,并将系统作为一个整体进行测试,以模仿用户的使用方式。 他们通常需要大量时间才能执行自己。
  • 对于如何有效地组合这些测试类型的深入示例,您可以看看Steve Freeman和Nat Pryce的 Tests指导的Growinging Oriented Oriented Software 。

但是在继续进行示例之前,还有一个问题需要讨论:

为什么这很重要?


阅读(代码)与写作所花费的时间比例远远超过10:1…
罗伯特·马丁

四个阶段模式的目的是使您易于理解测试正在验证的行为。 安装程序始终定义测试的前提条件,练习实际上会调用测试的行为,验证是否指定了预期的结果,而拆除工作完全与内部维护有关,正如Meszaros所说的那样。

这种干净的相分离清楚地表明了单个测试的意图,并提高了可读性。 该方法意味着测试一次只能验证给定输入状态的一种行为,因此通常没有条件块等(单条件测试)。

试图避免繁琐的夹具安装并在单一方法中测试尽可能多的功能虽然很诱人,但这通常会导致某种性质混淆 。 因此,请始终记住:如果不小心编写测试,可能会给维护和进步带来痛苦。

但是现在是时候继续进行示例了,看看这种新知识可以为我们做什么!

角落案例测试

完成快乐路径测试后,我们将继续指定极端情况行为。 对数字范围计数器的描述指出,数字序列应从给定值开始。 这一点很重要,因为它定义了计数器范围的下限(一个角…)。

将该值作为配置参数传递给NumberRangeCounter的构造函数似乎很合理。 适当的测试可以验证next返回的第一个数字是否等于此初始化:

@Testpublic void lowerBound() {NumberRangeCounter counter = new NumberRangeCounter( 1000 );int actual = counter.next();assertEquals( 1000, actual );}

再次,我们的测试类不会编译。 通过将lowerBound参数引入计数器的构造函数来解决此问题,则会在subsequentNumber测试中导致编译错误。 幸运的是,后一个测试被编写为独立于下限定义,因此该测试的夹具也可以使用该参数。

但是,测试中的原义数字是多余的,没有明确指出其目的。 后者通常表示为幻数 。 为了改善这种情况,我们可以引入一个常量LOWER_BOUND并替换所有文字值。 以下是测试类的外观:

public class NumberRangeCounterTest {private static final int LOWER_BOUND = 1000;@Testpublic void subsequentNumber() {NumberRangeCounter counter = new NumberRangeCounter( LOWER_BOUND );int first = counter.next();int second = counter.next();assertEquals( first + 1, second );}@Testpublic void lowerBound() {NumberRangeCounter counter = new NumberRangeCounter( LOWER_BOUND );int actual = counter.next();assertEquals( LOWER_BOUND, actual );}
}

查看代码,您可能会注意到夹具的在线设置对于两种测试都是相同的。 通常,内联设置由多个语句组成,但是测试之间通常存在共同点。 为了避免冗余,可以将共同之处委托给设置方法:

public class NumberRangeCounterTest {private static final int LOWER_BOUND = 1000;@Testpublic void subsequentNumber() {NumberRangeCounter counter = setUp();int first = counter.next();int second = counter.next();assertEquals( first + 1, second );}@Testpublic void lowerBound() {NumberRangeCounter counter = setUp();int actual = counter.next();assertEquals( LOWER_BOUND, actual );}private NumberRangeCounter setUp() {return new NumberRangeCounter( LOWER_BOUND );}
}

如果委托设置方法可以提高给定情况下的可读性,这是有争议的,但它会导致JUnit的一个有趣功能: 隐式执行公共测试设置的可能性。 这可以通过将@Before注释应用于不带返回值和参数的公共非静态方法来实现。

这意味着此功能需要付出一定的代价。 如果要消除测试中的多余setUp调用,则必须引入一个采用NumberRangeCounter实例的字段:

public class NumberRangeCounterTest {private static final int LOWER_BOUND = 1000;private NumberRangeCounter counter;@Beforepublic void setUp() {counter = new NumberRangeCounter( LOWER_BOUND );}@Testpublic void subsequentNumber() {int first = counter.next();int second = counter.next();assertEquals( first + 1, second );}@Testpublic void lowerBound() {int actual = counter.next();assertEquals( LOWER_BOUND, actual );}
}

显而易见, 隐式设置可以消除很多代码重复。 但是从测试的角度来看,它也引入了一种魔术,这会使阅读变得困难。 因此,对于“我应该使用哪种安装类型?”这个问题,答案很明确。 是:这取决于…

由于我通常会注意保持单位/测试较小,因此折衷似乎可以接受。 因此,我经常使用隐式设置来定义公共/快乐路径输入,并为每个极端案例测试通过小的内联/代理设置对它进行相应的补充。 否则,由于特别是初学者倾向于让测试变得更大,因此最好坚持使用内联和委托设置。

JUnit运行时确保在测试类的新实例上调用每个测试。 这意味着在我们的示例中,仅构造函数的灯具可以完全省略setUp方法。 可以通过隐式方式为counter字段分配新的 fixture:

private NumberRangeCounter counter = new NumberRangeCounter( LOWER_BOUND );

尽管有些人@Before使用它,但其他人则认为@Before注释方法会使意图更加明确。 好吧,我不会就此进行战争,让您自己决定的决定……

隐式拆解

想象一下,无论出于何种原因都需要处理NumberRangeCounter 。 这意味着我们必须在测试中添加拆卸阶段。 根据我们的最新代码片段,使用JUnit可以轻松实现,因为它支持使用@After注释进行隐式拆卸 。 我们只需要添加以下方法:

@Afterpublic void tearDown() {counter.dispose();}

如上所述,拆卸完全是关于客房清洁的,完全不对特定测试添加任何信息。 因此,隐式执行此操作通常很方便。 或者,即使测试失败,也必须使用try-finally构造来处理此问题,以确保执行拆解。 但是后者通常不会提高可读性。

预期的例外

一个特殊的极端情况是测试预期的异常。 出于示例考虑,如果next的调用超出给定范围的值量,则NumberRangeCalculator应该引发IllegalStateException 。 同样,通过构造函数参数配置范围可能是合理的。 使用try-catch构造,我们可以编写:

@Testpublic void exeedsRange() {NumberRangeCounter counter = new NumberRangeCounter( LOWER_BOUND, 0 );try {counter.next();fail();} catch( IllegalStateException expected ) {}}

好吧,这看起来有些丑陋,因为它模糊了测试阶段的分离,并且可读性不强。 但是由于Assert.fail()会引发AssertionError因此可以确保在没有引发异常的情况下测试失败。 并且catch块可以确保在抛出预期异常的情况下成功完成测试。

使用Java 8,可以使用lambda表达式编写结构清晰的异常测试。 有关更多信息,请参阅
使用Java 8 Lambdas清洁JUnit Throwable-Tests 。

如果足以验证是否已抛出某种类型的异常,则JUnit通过@Test批注的expected方法提供隐式验证 。 上面的测试可以写成:

@Test( expected = IllegalStateException.class )public void exeedsRange() {new NumberRangeCounter( LOWER_BOUND, ZERO_RANGE ).next();}

尽管此方法非常紧凑,但也很危险。 这是因为不能区分是在设置的建立阶段还是在测试的执行阶段抛出了给定的异常。 因此,如果构造函数意外IllegalStateException则测试将是绿色的,因此毫无价值。

JUnit提供了第三种可能性,可以更清晰地测试预期异常,即ExpectedException规则。 由于我们还没有涵盖规则 ,并且该方法有点扭曲了四个阶段的结构,因此我将对该主题的明确讨论推迟到有关规则和运行者的后续文章上并且仅提供摘要作为预告片:

public class NumberRangeCounterTest {private static final int LOWER_BOUND = 1000; @Rulepublic ExpectedException thrown = ExpectedException.none();@Testpublic void exeedsRange() {thrown.expect( IllegalStateException.class );new NumberRangeCounter( LOWER_BOUND, 0 ).next();}[...]
}

但是,如果您不想等待,可以在RafałBorowiec的 《 JUNIT EXPECTEDEXCEPTION RULE:BEYOND BASICS 》一文中详细了解一下。

结论

简而言之,JUnit的这一章解释了通常用于编写单元测试的四个阶段结构-设置,练习,验证和拆卸。 它描述了每个阶段的目的,并着重强调了在一致使用时如何提高测试用例的可读性。 该示例在极端案例测试的上下文中加深了该学习材料。 希望它具有足够的平衡性,可以提供容易理解的介绍而又不琐碎。 改进建议当然受到高度赞赏。

本教程的下一章将继续该示例,并介绍如何处理单元依赖性和测试隔离,敬请关注。

参考文献

  • [MES] xUnit测试模式,第19章,四阶段测试,Gerard Meszaros,2007年
  • [MAR1]清洁规范,第9章:单元测试,第130页及以下,Robert C. Martin,2009年
  • [KAC]使用JUnit和Mockito进行的实用单元测试,3.9。 单元测试的阶段,Tomek Kaczanowski,2013年
  • [MAR2]清洁代码,第9章:单元测试,第127页,Robert C. Martin,2009年

翻译自: https://www.javacodegeeks.com/2014/08/junit-in-a-nutshell-test-structure.html

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

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

相关文章

cf1207解题报告

cf1207解题报告 A 模拟 #include <bits/stdc.h> #define ll long long using namespace std; ll T,a,b,c,x,y; int main() {cin>>T;while(T --> 0) {cin>>a>>b>>c>>x>>y;ll ans0;if(x>y) {while(a>2&&b>1) ansx…

Oracle 用脚本安装第二个数据库

安装第二个数据库&#xff1a;登录oracle用户进入家目录&#xff0c;添加配置环境变量&#xff1a;vi .bash_profier ORACLE_SIDprod2临时环境变量&#xff1a;$export ORACLE_HOME/u01/app/oracle/product/11.2.0/db_1 $export ORACLE_SIDprod2创建第二个数据库文件目录&#…

深入学习jQuery鼠标事件

前面的话 鼠标事件是DOM事件中最常用的事件&#xff0c;jQuery对鼠标事件进行了封装和扩展。本文将详细介绍jQuery鼠标事件 类型 鼠标事件共10类&#xff0c;包括click、contextmenu、dblclick、mousedown、mouseup、mousemove、mouseover、mouseout、mouseenter和mouseleave c…

智能自动PPR更改事件策略

ADF开发人员普遍认为&#xff0c;将迭代器绑定更改事件策略设置为ppr在性能方面不是一件好事&#xff0c;因为此策略会强制框架刷新每个请求上绑定到此迭代器的所有属性绑定。 这不是真的&#xff01; 框架仅刷新在请求期间已更改的属性和依赖于已更改属性的属性。 让我们考虑…

[转]国际化: 理解Java平台上的Locale

From:http://jatula.javaeye.com/blog/183680 语言和地理环境对我们的文化产生重要影响.我们同他人之间的交流以及生活中的事件都发生在语言和地理环境所产生的一个系统里.由于语言和环境的不同,以至 需要我们来制定一个适合的方式来达到向他人表述我们自己或者我们的想法的目的…

深入学习jQuery描述文本内容的3个方法

前面的话 在javascript中&#xff0c;描述元素内容有5个属性&#xff0c;分别是innerHTML、outerHTML、innerText、outerText和textContent。这5个属性各自有各自的功能&#xff0c;且兼容性不同。jQuery针对这样的处理提供了3个便捷的方法&#xff0c;分别是&#xff1a;html(…

luoguP4551最长异或路径

P4551最长异或路径 链接 luogu 思路 从\(1\)开始\(dfs\)求出\(xor\)路径。然后根据性质\(x\)到\(y\)的\(xor\)路径就是\(xo[x]^xo[y]\) 代码 #include <bits/stdc.h> using namespace std; const int _1e57; int xo[_],w[_],ans-1,num0; struct node {int v,q,nxt; }e[_&…

谈一谈我的996 (随笔)

说一说996这个最近技术圈比较热门的话题。 什么是996&#xff0c;早九晚九一周上班6天。 看朋友圈&#xff0c;有个朋友说自己没有经历过什么是996&#xff0c;感觉自己是个假的程序员&#xff0c;是不是程序员就应该加班呢&#xff0c;是不是已经下意识&#xff0c;大众性的认…

装饰器设计模式的应用

嗨&#xff0c;您好&#xff01; 今天&#xff0c;我将展示装饰设计模式的实际应用。 装饰器设计模式是一种广泛使用的设计模式&#xff0c;同时在运行期间处理图形&#xff0c;树木和动态更改。 如果您正在寻找或尝试进行递归&#xff0c;这也是一个不错的选择。 我喜欢它。…

ubuntu postgresql 的安装

本人亲测&#xff0c;在ubuntu9.10上安装的postgresql 8.3版本。郁闷了好几天&#xff0c;终于ok了。sudo apt-get install postgresql-8.3 postgresql-client-8.3 postgresql-contrib-8.3然后在/etc/profile里加上export POSTGRES_HOME/usr/lib/postgresql/8.3export PGLIB$PO…

loj2090. 「ZJOI2016」旅行者

loj2090. 「ZJOI2016」旅行者 链接 loj 思路 \((l,mid)(mid1,r)\).考虑跨过mid的贡献。 假设选的中间那条线的点为gzy,贡献为\(dis(x,gzy)dis(gzy,y)\) 那就计算n遍最短路,一次分治为\(n^2mlog{nm}\) 设Sn*m.矩阵的长度是不定的&#xff0c;每次取最长的边进行分治是最好的&…

利用select实现年月日三级联动的日期选择效果

前面的话 关于select控件&#xff0c;可能年月日三级联动的日期选择效果是最常见的应用了。本文是选择框脚本的实践&#xff0c;下面将对日期选择效果进行详细介绍 演示 style"width: 100%; height: 80px;" src"https://demo.xiaohuochai.site/js/date/d2.html&…

ubuntu资料

1、VNC实现Windows远程访问Ubuntu 16.04&#xff08;无需安装第三方桌面,直接使用自带远程工具&#xff09; https://www.cnblogs.com/xuliangxing/p/7642650.html 转载于:https://www.cnblogs.com/little-kwy/p/10761865.html

守护基于JVM的应用程序

部署体系结构设计是任何定制服务器端应用程序开发项目的重要组成部分。 由于其重要性&#xff0c;部署架构设计应尽早开始&#xff0c;并与其他开发活动一起进行。 部署体系结构设计的复杂性取决于许多方面&#xff0c;包括所提供服务的可伸缩性和可用性目标&#xff0c;部署过…

开篇

进入软件行业已有数日&#xff0c;但终归还算是新手。 对程序员有点好奇&#xff0c;所以选择这个职业&#xff0c;从现在开始希望能够记下自己从业当中的点点滴滴&#xff0c;同时希望自己也能够一直坚持下去。转载于:https://www.cnblogs.com/gaser/archive/2009/11/22/16082…

构造函数 基本使用

相关知识点&#xff1a; 构造函数、原型对象、实例对象 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><meta h…

2019CCPC网络预选赛 八道签到题题解

目录 2019中国大学生程序设计竞赛&#xff08;CCPC&#xff09; - 网络选拔赛6702 &6703 array6704 K-th occurrence6705 path6706 huntian oy6707 Shuffle Card6708 Windows Of CCPC6709 Fishing Master 2019中国大学生程序设计竞赛&#xff08;CCPC&#xff09; - 网络选…

深入理解表单脚本系列第一篇——表单对象

前面的话 javascript最初的一个应用就是分担服务器处理表单的责任&#xff0c;打破处处依赖服务器的局面。尽管目前的web和javascript已经有了长足的发展&#xff0c;但web表单的变化并不明显。由于web表单没有为许多常见任务提供现成的解决方法&#xff0c;很多开发人员不仅会…

stm32开发问题集锦

1 在flash中跑程序时&#xff0c;能进入中断&#xff0c;但在ram中跑时&#xff0c;进不了中断的原因。看以下的中断配置函数可以知道&#xff0c;要在ram中调试程序&#xff0c;需要定义VECT_TAB_RAM。定义方法a:在Project\Options for taget xxx 的对话框的c/c中定义宏VECT_T…

luoguP4213 【模板】杜教筛(Sum)杜教筛

链接 luogu 思路 为了做hdu来学杜教筛。 杜教筛模板题。 卡常数&#xff0c;我加了register居然跑到不到800ms。 太深了。 代码 // luogu-judger-enable-o2 #include <bits/stdc.h> #define ll long long using namespace std; const int _5000030; int vis[_],pri[_],cn…