我的编程空间,编程开发者的网络收藏夹
学习永远不晚

如何分析JUnit 5中的Extension Model扩展模型

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

如何分析JUnit  5中的Extension Model扩展模型

这篇文章主要为大家分析了如何分析JUnit  5中的Extension Model扩展模型的相关知识点,内容详细易懂,操作细节合理,具有一定参考价值。如果感兴趣的话,不妨跟着跟随小编一起来看看,下面跟着小编一起深入学习“如何分析JUnit  5中的Extension Model扩展模型”的知识吧。

JUnit 4 的扩展模型

我们先来看看 JUnit 4 中是如何实现扩展的。在 JUnit 4  中实现扩展主要是通过两个,有时也互有重叠的扩展机制:运行器(Runners)和规则(Rules)。

运行器(Runners)

测试运行器负责管理诸多测试的生命周期,包括它们的实例化、setup/teardown 方法的调用、测试运行、异常处理、发送消息等。在 JUnit 4  提供的运行器实现中,它负责了这所有的事情。

在 JUnit 4 中,扩展 JUnit  的唯一方法是:创建一个新的运行器,然后使用它标记你新的测试类:@Runwith(MyRunner.class)。这样 JUnit  就会识别并使用它来运行测试,而不会使用其默认的实现。

这个方式很重,对于小定制小扩展来说很不方便。同时它有个很苛刻的限制:一个测试类只能用一个运行器来跑,这意味着你不能组合不同的运行器。也即是说,你不能同时享受到两个以上运行器提供的特性,比如说不能同时使用  Mockito 和 Spring 的运行器,等。

规则(Rules)

为了克服这个限制,JUnit 4.7 中引入了规则的概念,它是指测试类中特别的注解字段。 JUnit 4  会把测试方法(与一些其他的行为)包装一层传给规则。规则因此可以在测试代码执行前后插入,执行一些代码。很多时候在测试方法中也会直接调规则类上的方法。

这里有一个例子,展示的是 temporary folder (临时文件夹)规则:

public static class HasTempFolder {     @Rule     public TemporaryFolder folder= new TemporaryFolder();       @Test     public void testUsingTempFolder() throws IOException {         File createdFile= folder.newFile("myfile.txt");         File createdFolder= folder.newFolder("subfolder");         // ...     } }

因为 @Rule 注解的存在,JUnit 会先把测试方法 testUsingTempFolder 包装成一个可执行代码块,传给 folder  规则。这个规则的作用是执行时, 由 folder  创建一个临时目录,执行测试,测试完成后删除临时目录。因此,在测试内部可以放心地在临时目录下创建文件和文件夹。

当然还有其他的规则,比如允许你在 Swing 的事件分发线程中执行测试 的规则,负责连接和断开数据库的规则,以及让运行过久的测试直接超时的规则等。

规则特性其实已经是个很大的改进了,不过仍有局限,它只能在测试运行之前或之后定制操作。如果你想在此之外的时间点进行扩展,这个特性也无能为力了。

现状

总而言之,在 JUnit 4 中存在两种不同的扩展机制,两者均各有局限,并且功能还有重叠的部分。在 JUnit 4  下编写干净的扩展是很难的事。此外,即使你尝试组合两种不同的扩展方式,通常也不会一帆风顺,有时它可能根本不按照开发者期望的方式工作。

如何分析JUnit  5中的Extension Model扩展模型

JUnit 5 的扩展模型

Junit Lambda 项目成立伊始便有几点核心准则,其中一条便是“扩展点优于新特性”。这个准则其实也就是新版本 JUnit  中最重要的扩展机制了——并非唯一,但无疑是最重要之一。

扩展点

JUnit 5 扩展可以声明其主要关注的是测试生命周期的哪部分。JUnit 5  引擎在处理测试时,它会依次检查这些扩展点,并调用每个已注册的扩展。大体来说,这些扩展点出现次序如下:

  • 测试类实例 后处理

  • BeforeAll 回调

  • 测试及容器执行条件检查

  • BeforeEach 回调

  • 参数解析

  • 测试执行前

  • 测试执行后

  • 异常处理

  • AfterEach 回调

  • AfterAll 回调

(如果上面有你觉得不甚清晰或理解的点,请不用担心,我们接下来会挑其中的一些来讲解。)

每个扩展点都对应一个接口。接口方法会接受一些参数,一些扩展点所处生命周期的上下文信息。比如,被测实例与方法、测试的名称、参数、注解等信息。

一个扩展可以实现任意个以上的接口方法,引擎会在调用它们时传入相应的上下文信息作为参数。有了这些信息,扩展就可以放心地实现所需的功能了。

无状态

这里我们需要考虑一个重要的细节:引擎对扩展实例的初始化时间、实例的生存时间未作出任何规约和保证,因此,扩展必须是无状态的。如果一个扩展需要维持任何状态信息,那么它必须使用  JUnit 提供的一个仓库(store)来进行信息读取和写入。

这样做的原因有几个:

  • 扩展的初始化时机和方式对引擎是未知的(每个测试实例化一次?每个类实例化一次?还是每次运行实例化一次?)。

  • JUnit 不想额外维护和管理每个扩展创建的实例。

  • 如果扩展之间想要进行通信,那么无论如何 JUnit 都必须提供一个数据交互的机制。

应用扩展

创建完扩展后,接下来需要做的就仅仅是告诉 JUnit  它的存在。这可以通过在需要使用该扩展的测试类或测试方法上添加一个@ExtendWith(MyExtension.class) 简单实现。

其实,还有另一种更简明的方式。不过要理解那种方式,我们必须先看一下 JUnit 的扩展模型中还有哪些内容。

自定义注解

JUnit 5 的 API  大部分是基于注解的,而且引擎在检查注解时还做了些额外的工作:它不仅会查找字段、类、参数上应用的注解,还会注解上的注解。引擎会把找到的所有注解都应用到被注解元素上。注解另一个注解可以通过所谓的元注解做到,酷的是  Junit 提供的所有注解都说得上是元注解了。

它的意义在于,JUnit 5 中我们就能够创建并组合不同的注解了,并且它们具备组合多个注解特性的能力:

 @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Test @Tag("integration") public @interface IntegrationTest { }

这个自定义的“集成测试”注解 @IntegrationTest 可以这样使用:

@IntegrationTest void runsWithCustomAnnotation() {     // this gets executed     // even though `@IntegrationTest` is not defined by JUnit }

进一步我们可以为扩展使用更简明的注解:

@Target({ ElementType.TYPE, ElementType.METHOD, ElementType.ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(ExternalDatabaseExtension.class) public @interface Database { }

现在我们可以直接使用 @Database  注解了,而不需要再声明测试应用了特定的扩展@ExtendWith(ExternalDatabaseExtension.class)。并且由于我们把注解类型  ElementType.ANNOTATION_TYPE 也添加到扩展支持的目标类型中去了,因此该注解也可以被我们或他人进一步的使用、组合。

例子

假设现在有个场景,我想量化一下测试运行花费的时间。首先,可以先创建一个我们想要的注解:

@Target({ TYPE, METHOD, ANNOTATION_TYPE }) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(BenchmarkExtension.class) public @interface Benchmark { }

注解声明其应用了 BenchmarkExtension 扩展,这是我们接下来要实现的。TODOLIST 如下:

  • 计算所有测试类的运行时间,在所有测试执行前保存其起始时间

  • 计算每个测试方法的运行时间,在每个测试方法执行前保存其起始时间

  • 在每个测试方法执行完毕后,获取其结束时间,计算并输出该测试方法的运行时间

  • 在所有测试类执行完毕后,获取其结束时间,计算并输出所有测试的运行时间

  • 以上操作,仅对所有注解了 @BenchMark 的测试类或测试方法生效

最后一点需求可能不是一眼便能发现。如果一个方法并未注解 @Benchmark 注解,它有什么可能被我们的扩展处理?  一个语法上的原因是,如果一个扩展被应用到了一个类上,那么它默认也会应用到类中的所有方法上。因此,如果我们的需求是计算整个测试类的运行时间,但不需具体到类中每个单独方法的运行时间时,类中的测试方法就必须被手动排除。这点我们可以通过单独检查每个方法是否应用了注解来做到。

有趣的是,需求的前四点与扩展点中的其中四个是一一对应的:BeforeAll、BeforeTestExecution、AfterTestExecution  与 AfterAll。因此我们要做的任务便是实现这四个对应的接口。具体实现很简单,把上面说的翻译成代码即是:

public class BenchmarkExtension implements         BeforeAllExtensionPoint, BeforeTestExecutionCallback,         AfterTestExecutionCallback, AfterAllExtensionPoint {       private static final Namespace NAMESPACE =             Namespace.of("BenchmarkExtension");       @Override     public void beforeAll(ContainerExtensionContext context) {         if (!shouldBeBenchmarked(context))             return;           writeCurrentTime(context, LaunchTimeKey.CLASS);     }       @Override     public void beforeTestExecution(TestExtensionContext context) {         if (!shouldBeBenchmarked(context))             return;           writeCurrentTime(context, LaunchTimeKey.TEST);     }       @Override     public void afterTestExecution(TestExtensionContext context) {         if (!shouldBeBenchmarked(context))             return;           long launchTime = loadLaunchTime(context, LaunchTimeKey.TEST);         long runtime = currentTimeMillis() - launchTime;         print("Test", context.getDisplayName(), runtime);     }       @Override     public void afterAll(ContainerExtensionContext context) {         if (!shouldBeBenchmarked(context))             return;           long launchTime = loadLaunchTime(context, LaunchTimeKey.CLASS);         long runtime = currentTimeMillis() - launchTime;         print("Test container", context.getDisplayName(), runtime);     }       private static boolean shouldBeBenchmarked(ExtensionContext context) {         return context.getElement()                 .map(el -> el.isAnnotationPresent(Benchmark.class))                 .orElse(false);     }       private static void writeCurrentTime(             ExtensionContext context, LaunchTimeKey key) {         context.getStore(NAMESPACE).put(key, currentTimeMillis());     }       private static long loadLaunchTime(             ExtensionContext context, LaunchTimeKey key) {         return (Long) context.getStore(NAMESPACE).remove(key);     }       private static void print(             String unit, String displayName, long runtime) {         System.out.printf("%s '%s' took %d ms.%n", unit, displayName, runtime);     }       private enum LaunchTimeKey {         CLASS, TEST     } }  「译者:啊这代码让人心旷神怡。」

上面代码有几个地方值得留意。首先是 shouldBeBenchmarked 方法,它使用了 JUnit 的 API  来获取当前元素是否(被元)注解了@Benchmark 注解;其次, writeCurrentTime / loadLaunchTime 方法中使用了 Junit  提供的 store 以写入和读取运行时间。

关于“如何分析JUnit  5中的Extension Model扩展模型”就介绍到这了,更多相关内容可以搜索编程网以前的文章,希望能够帮助大家答疑解惑,请多多支持编程网网站!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

如何分析JUnit 5中的Extension Model扩展模型

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

如何分析JUnit 5中的Extension Model扩展模型

这篇文章主要为大家分析了如何分析JUnit 5中的Extension Model扩展模型的相关知识点,内容详细易懂,操作细节合理,具有一定参考价值。如果感兴趣的话,不妨跟着跟随小编一起来看看,下面跟着小编一起深入学习“如何分析JUnit
2023-06-17

PyTorch中如何进行模型的解释性分析

PyTorch提供了多种方法来进行模型的解释性分析,以下是一些常用的方法:特征重要性分析:可以使用SHAP(SHapley Additive exPlanations)库来计算特征的重要性,帮助理解模型的预测结果是如何基于输入特征而变化的。
PyTorch中如何进行模型的解释性分析
2024-03-05

如何使用Python中的数据分析库和可视化工具对大规模数据进行处理和展示

如何使用Python中的数据分析库和可视化工具对大规模数据进行处理和展示,需要具体代码示例数据分析和可视化是现代科学和商业决策的关键工具。Python是一种功能强大且易于使用的编程语言,具有丰富的数据分析库和可视化工具,如NumPy、Pan
2023-10-22

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录