简而言之:JRunner

关于JUnit测试要点的多篇教程的第四章介绍了该工具可交换测试运行器体系结构的目的,并介绍了一些可用的实现。 正在进行的示例通过编写参数化测试的不同可能性扩大了主题。

由于我已经发布了JUnit Rules的介绍,因此我决定跳过关于该主题的已宣布部分。 相反,我对后者进行了较小的更新。

测试跑步者架构

不要害怕为了伟大而放弃美好。
约翰·洛克菲勒

在先前的文章中,我们学习了将某些xUnit测试模式[MES]与JUnit一起使用。 工具运行时的默认行为很好地支持了这些概念。 但是有时需要针对特定​​的测试类型或目标更改或补充后者。

考虑例如集成测试 ,该测试通常需要在特定环境中运行。 或想象一组包含子系统规范的测试用例,应该为常见的测试执行而组成。

为此,JUnit支持使用各种类型的测试处理器。 因此,它在运行时将测试类实例化,测试执行和结果报告委托给此类处理器,这些处理器必须是org.junit.Runner子类型。

测试用例可以使用@RunWith注释指定其预期的运行器类型。 如果未指定任何类型,那么运行时将选择BlockJUnit4ClassRunner作为默认值。 负责每个测试运行一个新的测试实例,并调用诸如隐式设置或拆卸处理程序之类的生命周期方法(另请参见有关测试结构的章节)。

@RunWith( FooRunner.class )
public class BarTest {

该代码段显示了如何将虚构的FooRunner指定为也是虚构的BarTest测试处理器。

通常,无需编写自定义测试运行程序。 但是,如果需要的话, Michael Scharhag最近就对JUnit的运行器体系结构进行了很好的解释。

看起来特殊测试运行程序的用法很简单,所以让我们看一下其中的几个:

套房和类别

Suite可能是最著名的处理器之一。 它允许以分层或主题结构的方式运行测试和/或其他套件的集合。 注意,指定类本身通常没有主体实现。 它带有一系列测试类的注释,这些类通过运行套件来执行:

@RunWith(Suite.class)
@SuiteClasses( { NumberRangeCounterTest.class,// list of test cases and other suites
} )
public class AllUnitTests {}

但是,套件的结构功能受到一定限制。 因此,JUnit 4.8引入了鲜为人知的Categories概念。 这样就可以定义自定义类别类型,例如单元测试,集成测试和验收测试。 要将测试用例或方法分配给这些类别之一,必须提供Category注释:

// definition of the available categories
public interface Unit {}
public interface Integration {}
public interface Acceptance {}// category assignment of a test case
@Category(Unit.class)
public class NumberRangeCounterTest {[...]
}// suite definition that runs tests
// of the category 'Unit' only
@RunWith(Categories.class)
@IncludeCategory(Unit.class)
@SuiteClasses( { NumberRangeCounterTest.class,// list of test cases and other suites
} )
public class AllUnitTests {}

Categories注释类定义了只运行与指定类别匹配的类列表测试的套件。 通过包含和/或排除注释来完成规范。 请注意,类别可以在Maven或Gradle构建中使用,而无需定义特定的套件类(请参见JUnit文档的类别部分)。

有关类别的更多信息: John Ferguson Smart撰写了有关使用JUnit类别对测试进行分组的详细说明。

由于通常认为套件类列表和类别注释的维护有些繁琐,因此您可能更喜欢通过测试后缀名称àFooUnitTest而不是FooTest进行分类。 这允许在运行时在类型范围上过滤类别。

但是JUnit本身不支持此过滤,因此您可能需要一个特殊的运行器来动态收集可用的匹配测试。 Johannes Link的ClasspathSuite是提供适当实现的库。 如果您碰巧在OSGi环境中进行集成测试,则Rüdiger的BundleTestSuite会对捆绑BundleTestSuite执行类似的操作。

在对如何将测试运行程序用于测试捆绑的第一印象之后,让我们继续本教程的示例,并进行一些更令人兴奋的事情。

参数化测试

本教程中使用的示例都是关于编写一个简单的数字范围计数器,该计数器从给定值开始传递一定数量的连续整数。 另外,计数器取决于存储类型以保留其当前状态。 有关更多信息,请参阅前面的章节。

现在,假定应将由构造函数参数初始化的NumberRangeCounter作为API提供。 因此,我们可以认为实例创建检查给定参数的有效性是合理的。

我们可以指定适当的极端情况,并通过每个测试通过IllegalArgumentException予以确认。 使用带有Java 8 Lambdas方法的Clean JUnit Throwable-Tests方法,这种验证storage参数一定不能为null的测试可能看起来像这样:

@Testpublic void testConstructorWithNullAsStorage() {Throwable actual = thrown( () -> new NumberRangeCounter( null, 0, 0 ) );assertTrue( actual instanceof IllegalArgumentException );assertEquals( NumberRangeCounter.ERR_PARAM_STORAGE_MISSING,actual.getMessage() );}

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

为了使该职位保持范围,我还跳过了有关NPE是否比IAE更好的讨论。

如果我们必须涵盖很多这种极端情况,则上述方法可能会导致很多非常相似的测试。 JUnit提供了Parameterized实现,以减少这种冗余。 这个想法是为通用测试结构提供各种数据记录。

为此,使用带有@Parameters注释的公共静态方法来创建数据记录作为对象数组的集合。 此外,测试用例需要一个带有参数的公共构造函数,该参数与记录提供的数据类型匹配。

参数化处理器针对由参数方法提供的每个记录运行给定测试。 这意味着对于每种测试和记录组合,都会创建一个新的测试类实例。 构造函数参数将存储为字段,并且可以通过测试进行访问以进行设置,练习和验证:

@RunWith( Parameterized.class )
public class NumberRangeCounterTest {private final String message;private final CounterStorage storage;private final int lowerBound;private final int range;@Parameterspublic static Collection<Object[]> data() {CounterStorage dummy = mock( CounterStorage.class );return Arrays.asList( new Object[][] { { NumberRangeCounter.ERR_PARAM_STORAGE_MISSING, null, 0, 0 }, { NumberRangeCounter.ERR_LOWER_BOUND_NEGATIVE, dummy, -1, 0 },[...] // further data goes here... } );}public NumberRangeCounterTest(String message, CounterStorage storage, int lowerBound, int range ){this.message = message;this.storage = storage;this.lowerBound = lowerBound;this.range = range;}@Testpublic void testConstructorParamValidation() {Throwable actual = thrown( () -> new NumberRangeCounter( storage, lowerBound, range ) );assertTrue( actual instanceof IllegalArgumentException );assertEquals( message, actual.getMessage() );}[...]
}

尽管该示例确实减少了测试冗余,但至少在可读性方面值得商bat。 最后,这通常取决于测试的数量和特定测试数据的结构。 但绝对不幸的是, 使用任何记录值的测试也将执行多次。

因此,参数化测试通常保存在单独的测试用例中,通常感觉更像是一种解决方法,而不是适当的解决方案。 因此,一个聪明的人想到了提供一种可以避免上述问题的测试处理器的想法。

JUnitParams

JUnitParams库提供JUnitParamsRunner@Parameter类型。 param批注指定给定测试的数据记录。 注意使用相同简单名称的JUnit批注的区别。 后者标记了提供数据记录的方法!

可以使用JUnitParams重写上面的测试方案,如以下代码片段所示:

@RunWith( JUnitParamsRunner.class )
public class NumberRangeCounterTest {public static Object data() {CounterStorage dummy = mock( CounterStorage.class );return $( $( ERR_PARAM_STORAGE_MISSING, null, 0, 0 ),$( ERR_LOWER_BOUND_NEGATIVE, dummy, -1, 0 ) );  }@Test@Parameters( method = "data" )public void testConstructorParamValidation(String message, CounterStorage storage, int lowerBound, int range ) {Throwable actual = thrown( () -> new NumberRangeCounter( storage, lowerBound, range ) );assertTrue( actual instanceof IllegalArgumentException );assertEquals( message, actual.getMessage() );}[...]
}

虽然这当然更紧凑并且乍一看看上去更干净,但仍有一些构造需要进一步说明。 $(...)方法是在JUnitParamsRunner (静态导入)中定义的,是创建对象数组的快捷方式。 一旦习惯了,数据定义就会变得更具可读性。

$快捷方式用于方法data以创建嵌套的对象数组作为返回值。 尽管运行程序期望在运行时嵌套数据数组,但它能够将简单的对象类型作为返回值来处理。

测试本身具有一个附加的@Parameters批注。 批注的方法声明是指用于为测试提供声明的参数的数据提供程序 。 方法名称在运行时通过反射解析。 这是解决方案的缺点,因为它在编译时不安全。

但是在其他用例场景中,您可以指定数据提供程序类或隐式值,因此不会受到这种折衷的影响。 有关更多信息,请参见例如该库的快速入门指南 。

另一个巨大的优点是,现在只有那些测试针对使用@Parameters批注的数据记录运行。 标准测试仅执行一次。 反过来,这意味着可以将参数化测试保留在设备的默认测试用例中。

junit-params-test

包起来

以上各节概述了JUnit可交换测试运行器体系结构的意义和目的。 它介绍了套件和类别以显示基本用法,并举例说明了测试运行程序如何简化编写与数据记录相关的测试的任务。

有关其他测试运行程序的列表,junit.org上的“ 测试运行程序”和“ 自定义运行程序 ”页面可能是一个不错的起点。 并且,如果您想知道标题图片的Theories主题是什么,可以看看Florian Waibels在JUnit上 发表的文章– Practice和@Theory之间的区别 。

下次在Nutshell中使用JUnit时,我最后将介绍可用于验证测试结果的各种断言。

参考文献

[MES] xUnit测试模式,Gerard Meszaros,2007年

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

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

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

相关文章

cmake how to create vs file filters

cmake how to create vs file filters 用cmakelists构建出来的工程&#xff0c;没有文件filters&#xff0c;可采用如下方法解决 set(SOURCE_LIST"lotteryTicket.cpp""stdafx.cpp""stdafx.h""test/main.cpp" )add_executable(lotteryT…

Cannot retrieve mapping for action

想必用过Struts的朋友都遇到过这个异常吧&#xff01;没遇到的也可能&#xff0c;只能说你很强或运气不错。 我遇到该异常的解释是我不强&#xff0c;用Struts不是很多&#xff0c;或者说根本不熟练&#xff0c;对一些知识了解得并不深&#xff0c;仅仅皮毛而已&#xff0c;所以…

休眠字节码增强

介绍 既然您了解了Hibernate脏检查的基础知识 &#xff0c;我们就可以研究增强的脏检查机制。 虽然默认的图遍历算法对于大多数用例可能已足够&#xff0c;但有时您需要优化的脏检查算法&#xff0c;并且检测方法比构建自己的自定义策略更方便。 使用Ant休眠工具 传统上&#…

Hibernate核心接口

一、Configuration类&#xff1a;1、 作用&#xff1a;&#xff08;1&#xff09;管理hibernate配置信息&#xff08;2&#xff09;读取hibernate.cfg.xml文件&#xff08;3&#xff09;加载hibernate的驱动&#xff0c;例如&#xff1a;url,用户名&#xff08;4&#xff09;管…

CSS实现垂直居中的方法

CSS实现垂直居中的方法 1、relative absolute定位&#xff1a; (1)css html代码 1 <!doctype html>2 <html lang"en">3 4 <head>5 <meta charset"UTF-8" />6 <title>Document</title>7 …

Neo4j:收集多个值

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

spring 概念理解(资料)

一、Spring的IoC(Inversion of Control)。这是Spring中得有特点的一部份。IoC又被翻译成“控制反转”&#xff0c;也不知道是谁翻译得这么别扭&#xff0c;感觉很深奥的词。其实&#xff0c;原理很简单&#xff0c;用一句通俗的话来说&#xff1a;就是用XML来定义生成的对象。I…

运用flask、flask-restful开发rest风格的接口,并使用蓝图增加代码的延展性和可扩展性。...

本人做为一个测试人员&#xff0c;之前也有写过&#xff0c;想要测试好接口&#xff0c;那必须要知道如何开发一个接口的重要性。 之前也写过通flask或者flask-retful开发接口&#xff0c;但那些只是一些最简单的demo&#xff0c;不具有很好延展性和扩展性。 此次我们带一整个完…

2014年夏末大Java新闻

正如即将到来的JavaOne那样 &#xff0c;最近在Java社区中已经有很多重大新闻。 这篇文章简要地引用了其中的三个项目&#xff08;Java SE 8更新&#xff0c;Java SE 9和Java EE 8&#xff09;&#xff0c;并对我发现这是我在类路径/类加载器问题上见过的更清楚的文章之一进行了…

初学 Ajax(涉及 php)

一直知道 ajax 但是尚未真正了解&#xff0c; 这次看了慕课网的《Ajax全接触》&#xff0c;算是有所收获&#xff0c;入了个门。需要用到php&#xff0c;因为 Ajax也是向服务器请求&#xff08;不知道这么解释对不对&#xff09;&#xff0c; 所以还需要配置环境&#xff0c; …

php分页显示

<?php /*** Page Class* 实现各种分页样式* author yangsh*/ class Pager {/*** 数据总数* var integer*/private $totalItems;/*** 每页显示数* var integer*/private $pageSize 20;/*** 页面显示的页码标号的数量* var integer*/private $codeNum 10;/*** 跳转链接* va…

DI容器是代码污染者

尽管依赖项注入 &#xff08;也称为“ DI”&#xff09;是一种在OOP中组成对象的自然技术&#xff08;在Martin Fowler引入该术语之前就已知道&#xff09;&#xff0c;但Spring IoC &#xff0c; Google Guice &#xff0c; Java EE6 CDI &#xff0c; Dagger和其他DI框架将其…

java程序-类的高级特性

创建Employee类&#xff0c;在类中定义三个属性&#xff1a;编号&#xff0c;姓名&#xff0c;年龄&#xff0c;然后在构造方法里初始化这三个属性&#xff0c;最后在实现接口中的定义的CompareTo方法&#xff0c;将对象按编号升序排列。 代码如下&#xff1a;(程序可能有些错误…

js 数组去重

数组去重的思路&#xff0c;突然感觉挺有趣的&#xff0c;来整理一下 两个 for 循环比较的&#xff0c;如下function removeRepeat(arr){ for( var i 0; i < arr.length; i ){ for ( var j i 1; j < arr.length; i ){ if ( a…

CSS中实现水平/垂直居中

CSS中实现水平/垂直居中 在CSS中实现水平居中相对简单&#xff0c;但是却没有一个明确的属性表示这是实现垂直居中的&#xff0c;这就导致垂直居中的实现相对初学者来说难上许多。但是在实际的开发中垂直居中的需求常常出现&#xff0c;例如一行中有左右两部分&#xff0c;左边…

SWF 文件不能访问本地资源 只有仅限于文件系统的 SWF 文件和可信的本地 SWF 文件可以访问本地资源。...

错误信息&#xff1a;SecurityError: Error #2148: SWF 文件 D:/demo/test/index.swf 不能访问本地资源 D:/demo/test/bin-debug/textLayout_4.0.0.10485.swf。只有仅限于文件系统的 SWF 文件和可信的本地 SWF 文件可以访问本地资源。 at flash.net::URLStream/load() at fla…

高并发系统之大忌-慢查询

最近又遇到了一次慢查把db&#xff08;mariadb10)几乎打挂的案例&#xff0c;作为一个核心支付系统的技术负责人&#xff0c;真是每日如履薄冰。因为之前支付系统经常出问题&#xff0c;现在各个BG对支付系统都盯得很紧。这次要不是我及时让DB给暴力清理数据&#xff0c;没准又…

CSS媒体查询

格式&#xff08;style&#xff1a;{}&#xff09;元素设置&#xff1a; body{background-color:#0033FF;}/*媒体查询:当页面宽度最大为960px时*/media screen and (max-width: 960px){/*body背景颜色为*/body{background-color:#FF6699}}media screen and (max-width: 768px){…

(单元测试)JavaBeans的技巧

如果您正在编写Java代码&#xff0c;那么您至少要编写一些遵循JavaBean约定的类&#xff0c;即&#xff0c;具有带有公共getter和setter方法的私有属性的类&#xff0c;包含无参数的构造函数&#xff0c;可序列化&#xff0c;并且遵守Equals和HashCode合同。 最重要的是&#x…

js中的正则表达式

正则表达式等名称并不能让人一下就能明白是什么意思&#xff0c;我认为称之为 规则表达式 更为合理&#xff0c;就是描述一个字符串规则的表达式。 语法 正则表达式的语法有两种&#xff0c;下面第一种语法稍显 怪异&#xff0c;但确实是合法的并且相对方便&#xff0c;推荐。…