junit:junit_简而言之,JUnit:测试结构

junit: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…
罗伯特·C·马丁,清洁法规

四个阶段模式的目的是使您易于理解测试正在验证的行为。 安装程序总是定义测试的前提条件,练习实际上会调用测试的行为,验证是否指定了预期的结果,而拆除工作完全与内部维护有关,正如梅萨罗斯(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新鲜的夹具字段可以隐式进行:

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

junit:junit

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

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

相关文章

联想打印机7256显示更换墨盒_惠普打印机涉嫌垄断?只认自家“昂贵”墨盒,成本太高招架不住...

近日,有人爆料称,所在公司购买的惠普打印机使用原装墨盒时可正常打印,更换其他品牌墨盒后也不能正常使用,因此认为惠普公司有行业垄断的嫌疑。从细节来看,该公司购买了惠普同一型号但不同批次的两台打印机,…

linux mcelog 运行,服务器硬件检测(采用mcelog)

mt 内存监控:mcecheck.pyraid监控: check-raidmcelog 是 x86 的 Linux 系统上用来检查硬件错误,特别是内存和CPU错误的工具。安装方式yum install mcelog运行mcelog查看日志方式/var/log/mcelogMCE 0HARDWARE ERROR. This is NOT a software …

动手选择值

由于冠状病毒的存在,可选的东西在空中,一切都变得可选,例如可选的公共聚会,可选的在家工作,可选的旅行等。 我现在是时候谈论处理NULL引用的软件工程中真正的“ 可选 ”了。 托尼霍尔(Tony Hoare&#xf…

python mysql操作_Python的MySQL操作

Python的DB-API,为大多数的数据库实现了接口,使用它连接各数据库后,就可以用相同的方式操作各数据库。Python DB-API使用流程:引入API模块。获取与数据库的连接。执行SQL语句和存储过程。关闭数据库连接。一、安装MySQL客户端MySQLdb 是用于Python链接Mysql数据库的接口&#x…

linux下的遥控器软件下载,Linux操作系统下遥控器的配置及使用方法

你有没有想象过能够坐在沙发上,或者躺在床上,拿着遥控器像操作电视一样来操作电脑?可能你已经见到过市场上出现的那种电脑遥控器,不过它们都是基于windows下的。其实,通过一定的配置,在linux平台上&#xf…

wincc历史数据库_WinCC系统的基本功能介绍——自动化工程师必备

写在面前前面讲解了西门子的TIA Portal Wincc, Wincc Classic和Wincc OA (一文带你了解西门子Wincc),介绍了西门子的超大型/分布式SCADA系统Wincc OA(初识西门子Wincc OA——超大型/分布式SCADA),还介绍了Wincc Classic的典型架构和选型指南(WinCC V7.5典型架构及选…

apache.camel_Apache Camel 2.14中的更多指标

apache.camelApache Camel 2.14将于本月晚些时候发布。 由于正在解决某些Apache基础结构问题,因此存在一些问题。 这篇博客文章讨论的是我们添加到此版本中的新功能之一。 感谢Lauri Kimmel捐赠了骆驼指标组件,我们将其与出色的Codehale指标库集成在一起…

获取linux详细信息,Linux 获取网口详细信息

一般来说,研究 ifconfig.c 源代码就可以达到目的了。但是Linux已经提供了比较方便的获取网口信息的方式:[philipcatonbj ~]$ cat /sys/class/net/em1/statistics/rx_bytes3911191274在/sys/class/net/INTERFACE/statistics/ 目录下有所有网口的状态&…

python魔法方法str_8.9.魔法方法 - str()方法

# \_\_str\_\_()方法~~~class Car(object):"""定义了一个车类,可以启动和炸街"""def __init__(self, name, max_speed, vehicle_length):""" __init__() 方法,用来做变量初始化 或 赋值 操作""&…

依赖管理和Maven

Maven伟大而成熟。 几乎所有事物都总有解决方案。 您可能在组织项目上遇到的主要情况是依赖管理。 而不是每个项目都没有自己的依赖关系,您需要一种集中化的方式来继承那些依赖关系。 在这种情况下,您可以在父舞会上声明托管依赖项。 在我的示例中&…

linux ps 代码,Linux ps命令详解(示例代码)

ps命令是Process Status的缩写, 用来列出系统中当前运行的那些进程. ps命令列出的是当前那些进程的快照,就是执行ps命令的那个时刻的那些进程,如果想要动态的显示进程信息,就可以使用top命令ps常见命令参数********* simple selection ******…

python hadoop streaming_Hadoop Streaming 使用及参数设置

1. MapReduce 与 HDFS 简介什么是 Hadoop ?Google 为自己的业务需要提出了编程模型 MapReduce 和分布式文件系统 Google File System,并发布了相关论文(可在 Google Research 的网站上获得:GFS、MapReduce)。Doug Cutting 和 Mike Cafarella …

neo4j set 多个值_Neo4j:收集多个值

neo4j set 多个值在Neo4j的密码查询语言中,我最喜欢的功能之一是COLLECT,它使我们能够将项目分组到一个数组中以备后用。 但是,我注意到人们有时难以确定如何使用COLLECT收集多个项目,并且很难找到一种方法。 考虑以下数据集&am…

linux继续执行上一个命令快捷键,整理了上linux 命令行上常用的 快捷键

整理了下linux 命令行下常用的 快捷键整理了下linux 命令行下常用的 快捷键1.CTRL u 删除正行你敲的命令。例如 : find . -name hoho按下CTRL U 后 正行都会被删除2.若是你只是想删除一个局部的命令的话,那么可以用CTRL w 以空格为分隔符 删除你的命令…

shell字段拼接日期_shell 脚本字符串拼接

在编写shell脚本的时候,难免会使用shell脚本的字符串拼接,不经常使用的话真的会忘记。本人写着一篇的目的也就是记录以下,到时候回过头来不用找的太麻烦。首先变量与变量拼接str1"123"str2"456"echo $str1$str2结果输出1…

Apache Kafka消费者再平衡

消费者重新平衡决定哪个消费者负责某些主题的所有可用分区的哪个子集。 例如,您可能有一个包含20个分区和10个使用者的主题。 在重新平衡结束时,您可能希望每个使用者都从2个分区中读取数据。 如果关闭了这些使用者中的10个,则可能会期望每个…

linux与虚拟化实验室,Linux·学习笔记(2)虚拟化与仿真

Linux支持的虚拟化1.完全虚拟化:为客户操作系统创建一个虚拟机实例,使客户操作系统可以不加修改地运行,虚拟机模拟底层硬件的某些部分,捕捉需要由管理程序(虚拟机监视器)进行仲裁的调用。要求所有的操作系统都是针对统一处理器架构…

证明没有例外

您如何证明虚无的存在? 你应该? 在我编写的某些测试中,尤其是围绕验证或围绕创建空对象的测试中,我真正想写的是这样的: assertThat( ... call some code ... ) .doesntThrow(); 您可以合理地编写如下内容。 您会发现…

tfidf处理代码_tfidf.txt

function [count,tf,idf,weight]tfidf(docs,term)%docs--input documents,cell型%term-- keywords也就是特征词提取,cell型%output:count--存放各个关键词出现的频率在整个文档中% wordnum--存放文档总的词汇数%测试用例%*****************************************…

linux系统ll历史,Linux操作系统原理笔记

在Linux操作系统内核内部,进程是通过一个链表,而且是一个双向链表来管理的。进程描述符:每一个进程都有其描述符,每一个描述符彼此之间都有关联性的。双向链表:一个进程内部可能包含多个线程。上下文切换(Context swtc…