当前位置: 首页 > news >正文

【JUnit实战3_06】第三章:JUnit 的体系结构(下)

JUnit in Action, Third Edition

《JUnit in Action》全新第3版封面截图

写在前面
本章的 上一篇 笔记主要梳理了 JUnit 4 的种种困境,这一篇就来看看 JUnit 5 如何破局。

文章目录

  • 第三章 JUnit 的体系结构(下)
    • 3.5 JUnit 5 的架构特点
    • 3.6 JUnit 5 架构图
    • 3.7 旧版 JUnit 功能扩展示例
      • 3.7.1 示例1:自定义 rules 规则
      • 3.7.2 示例2:自定义 runner 运行器
      • 3.7.3 示例3:内置 rules 演示抛异常下的测试
      • 3.7.4 示例4:内置 rules 演示临时文件与文件夹的测试

第三章 JUnit 的体系结构(下)

(接上篇)……市场亟盼更轻量的基于模块化设计的全新测试框架,于是 JUnit 5 应运而生。

3.5 JUnit 5 的架构特点

全新的 JUnit 5 采用了模块化的解决方案,在践行 关注点的逻辑分离 原则上,主要聚焦三个方面:

  • 为开发者提供编写测试的核心 API
  • 重构测试的发现和运行机制;
  • 提供能与主流 IDE 和热门构建工具轻松交互的新 API,以简化测试的运行。

这样就有了目前的三个固定模块:

  • JUnit Platform 平台模块:不仅提供了基于 JVM 启动测试的专属平台,全新的 API 还可以让测试代码很方便地在控制台、IDE 及构建工具上启动;
  • JUnit Jupiter 模型模块:提出了全新的单元测试编程模型与扩展模型。其命名 Jupiter 源自太阳系第五大行星木星——同时也是体积最大的行星;
  • JUnit Vintage 测试引擎:用于在平台上运行基于 JUnit 3JUnit 4 等历史版本的测试代码,实现向后兼容。

各模块的主要构件及用途梳理如下:

JUnit Platform

构件名称作用
junit-platform-commonsJUnit 的内部通用库,仅限 JUnit 框架内部使用。
junit-platform-console提供从控制台发现和执行 JUnit 平台测试的相关支持。
junit-platform-console-standalone包含所有依赖项的可执行 jar 包,是一个基于 Java 的命令行应用,可从控制台启动 JUnit 平台。
junit-platform-engine测试引擎的公共 API
junit-platform-launcher用于配置和启动测试计划的公共 API,通常被 IDE 和构建工具使用。
junit-platform-runner用于在 JUnit 4 环境下执行 JUnit 平台测试及测试套件的运行工具。
junit-platform-suite-api包含在 JUnit 平台配置测试套件的相关注解。
junit-platform-surefire-provider基于 MavenJUnit 平台发现和执行测试。
junit-platform-gradle-plugin基于 GradleJUnit 平台发现和执行测试。

JUnit Jupiter

构建名称作用
junit-jupiter-api用于编写测试和扩展的专属 API
junit-jupiter-engineJUnit Jupiter 专属测试引擎,仅用于运行时
junit-jupiter-paramsJUnit Jupiter 的参数化测试提供相关支持
junit-jupiter-migrationsupport提供从 JUnit 4 迁移到 JUnit Jupiter 的支持

JUnit Vintage 只包含一个 junit-vintage-engine 构件,专门用于运行 JUnit 3JUnit 4 的实测用例,毕竟在 2017 年正式推出 JUnit 5 以前,市面上还有相当多的代码库是基于旧版的 JUnit 4 编写的测试用例。这可能也是为什么作者会在下一章中单独讨论新旧 JUnit 的迁移过渡的主要原因吧。

3.6 JUnit 5 架构图

原图详见书中图 3.8、3.9(第 36 页)。

以下两张示意图就是本章要介绍的核心内容 —— JUnit 5 的框架结构。从大包大揽的单独的 jar 文件发展成多模块协同发展的新格局 ——

简要版:

Fig3.4

细致版:

Fig3.5

3.7 旧版 JUnit 功能扩展示例

为了加深印象,本章还基于 JUnit 4 演示了自定义 runner 运行器和自定义 rules 规则的写法,同时附带了两个 JUnit 4 内置的 rules —— ExpectedExceptionTemporaryFolder 的用法(主要是为下一章的版本升级做铺垫)。

必需的依赖项:

<dependency><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId><version>5.6.0</version><scope>provided</scope>
</dependency>

3.7.1 示例1:自定义 rules 规则

先来看自定义 rules 的例子,实现在测试方法前后输出一行文字内容:

public class CustomRule implements TestRule {private Statement base;private Description description;@Overridepublic Statement apply(Statement base, Description description) {this.base = base;this.description = description;return new CustomStatement(base, description);}private static class CustomStatement extends Statement {private final Statement base;private final Description description;public CustomStatement(Statement base, Description description) {this.base = base;this.description = description;}@Overridepublic void evaluate() throws Throwable {System.out.println(this.getClass().getSimpleName() + " " + description.getMethodName() + " has started");try {base.evaluate();} finally {System.out.println(this.getClass().getSimpleName() + " " + description.getMethodName() + " has finished");}}}
}// test class
public class CustomRuleTester {@Rulepublic CustomRule myRule = new CustomRule();@Testpublic void myCustomRuleTest() {System.out.println("Call of a test method");}
}

运行结果:

Fig3.6

注意

@Rule 注解除了像 L3 那样加到成员变量上,还可以加到注入字段的 getter 方法上(比成员变量先执行,也是官方文档的推荐做法):

public class CustomRuleTester2 {private CustomRule myRule = new CustomRule();@Rulepublic CustomRule getMyRule() {return myRule;}@Testpublic void myCustomRuleTest() {System.out.println("Call of a test method");}
}

最终实测效果也是一样的。

3.7.2 示例2:自定义 runner 运行器

自定义 runner 稍显复杂,需要通过 RunNotifier 对象手动指定测试用例的开头和结尾:

public class CustomTestRunner extends Runner {private final Class<?> testedClass;public CustomTestRunner(Class<?> testedClass) {this.testedClass = testedClass;}@Overridepublic Description getDescription() {return Description.createTestDescription(testedClass, this.getClass().getSimpleName() + " description");}@Overridepublic void run(RunNotifier notifier) {System.out.println("Running tests with " + this.getClass().getSimpleName() + ": " + testedClass);try {Object testObject = testedClass.newInstance();for (Method method : testedClass.getMethods()) {if (method.isAnnotationPresent(Test.class)) {notifier.fireTestStarted(Description.createTestDescription(testedClass, method.getName()));method.invoke(testObject);notifier.fireTestFinished(Description.createTestDescription(testedClass, method.getName()));}}} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {throw new RuntimeException(e);}}
}// test class
@RunWith(CustomTestRunner.class)
public class CalculatorTest {@Testpublic void testAdd() {Calculator calculator = new Calculator();double result = calculator.add(10, 50);
//      assertEquals(61, result, 0);MatcherAssert.assertThat("10 add 50 should be 60", result, Matchers.closeTo(61, 0.01));}
}

实测结果(故意改为报错的情况,用到了 Hamcrest 提供的断言方法):

Fig3.7

3.7.3 示例3:内置 rules 演示抛异常下的测试

需要用到内置 rulesExpectedException,对抛出的异常和异常信息的设置必须先于测试代码的执行:

public class RuleExceptionTester {@Rulepublic ExpectedException expectedException = ExpectedException.none();private Calculator calculator = new Calculator();@Testpublic void expectIllegalArgumentException() {expectedException.expect(IllegalArgumentException.class);expectedException.expectMessage("Cannot extract the square root of a negative value");calculator.sqrt(-1);}@Testpublic void expectArithmeticException() {expectedException.expect(ArithmeticException.class);expectedException.expectMessage("Cannot divide by zero");calculator.divide(1, 0);}
}

3.7.4 示例4:内置 rules 演示临时文件与文件夹的测试

对于需要临时创建文件夹或临时文件的测试场景,JUnit 4 也提供了内置的工具类 TemporaryFolder,可实现测试前的自动创建,并在测试完成后自动清理。

ExpectedException 类似,相关文件操作必须写在断言语句前面(L9、L10):

public class RuleTester {@Rulepublic TemporaryFolder folder = new TemporaryFolder();private static File createdFolder;private static File createdFile;@Testpublic void testTemporaryFolder() throws IOException {createdFolder = folder.newFolder("createdFolder");createdFile = folder.newFile("createdFile.txt");assertTrue(createdFolder.exists());assertTrue(createdFile.exists());}@AfterClasspublic static void cleanUpAfterAllTestsRan() {assertFalse(createdFolder.exists());assertFalse(createdFile.exists());}
}

注意:给出这四个示例的根本目的,是为了突出 JUnit 4 自定义扩展的难写和反射机制的种种弊端,为下一章 JUnit 5 的优化做铺垫。

后话

自此,关于 JUnit 架构演进的大致过程就梳理完了。不知道认真看到这里的朋友们是否也会和当初的我一样,对 JUnit 4 没有提前考虑可扩展性和模块化设计感到不解?对主流 IDE 的研发团队长期以来饱受 JUnit 4 反射机制折磨而无动于衷的态度感到不可思议?难道世界真就是一个巨大的草台班子吗?直到再次梳理并发表这篇笔记,我才终于恍然大悟。

借用当下流行的一句话:一代人有一代人的上甘岭。JUnit 4 诞生于“中世纪”的 2006 年,那时 EJB 才初露锋芒,互联网的春天还远未到来。当时 JUnit 4 的历史使命是纠正了 JUnit 3 的一些方向性错误(方法名必须以 test 开头、不支持功能扩展、解绑对 junit.framework.TestCase 的严格继承等)。若时光倒退到 2005 年末,相信 JUnit 4 依然会这样选,依然会只留下一个 jar 包文件,依然不会高瞻远瞩地预见到十年后移动互联网的到来。好在日拱一卒,功不唐捐——正是有了 JUnit 4 十余年来培植出的单元测试生态,才有了 JUnit 5 的厚积薄发,更加灵活多样的设计范式才能被业内普遍认可,并在当前的 AI 浪潮下催生出更具潜力的全新方法论和工具链生态。

而我们要做的,不过是永葆好奇心和批判思维,既不盲从“草台班子”的论调夜郎自大,也不为荒诞可笑的“末世论”、“劝退论”畏首畏尾。虽然全新的 JUnit 6.0 已于上月底在 GitHub 社区正式发布,不用再苦心酝酿十多年,我们也大可不必提前焦虑或者盲目喜新厌旧,只要持续深耕下去并拥抱未来,自己扎实走过的每一步,都会算数。

http://www.dtcms.com/a/511439.html

相关文章:

  • 使用injected Provider在remix中调试合约的坑 -- 时间(或者最新块)更新不及时
  • 丽水市莲都建设分局网站湖南微网站开发
  • 笔试-最小组合数
  • Web UI自动化时,通过autoIT的解决window控件
  • 电商网站建设建议网站前端交互功能案例分析
  • Qt——窗口
  • [人工智能-大模型-20]:对比 Copilot 与国产替代方案(如通义灵码、百度Comate)
  • c语言和网站建设的关系平台网站开发可行性分析
  • gcc编译的过程及每个过程的作用
  • ROS2[Humble] -- URDF Tutorial- 02-multipleshapes
  • C#实现二维码和条形码生成与打印
  • C#WPF如何跳转页面
  • 【高并发服务器】八、Poller描述符监控类实现
  • 用vs2013网站开发四川最好的网络优化公司
  • 如何开发一个 IDEA 插件通过 Ollama 调用大模型为方法生成仙侠风格的注释
  • 【论文精读】Latent-Shift:基于时间偏移模块的高效文本生成视频技术
  • unity基础学习笔记<上>
  • C# WPF Dragablz使用记录 TabControl选项卡可拖拽为单独界面或停靠
  • 机器人场景落地步入技术验证阶段,微美全息加快创新势能探索AI多元路径变革
  • YOLOv4 核心内容笔记
  • 网站开发工程师待遇家庭网站建设
  • 医疗门户网站模板wordpress3.8
  • iOS的多线程下数据安全和内存泄漏以及工具使用监测内存泄漏
  • 『CMake』关于使用CMake构建项目时的现代/传统指令
  • 请被人做网站怎么做倒计时网站
  • App开发框架调研对比
  • Ubuntu下载以及安装详解以及应用安装
  • 亚马逊云代理:AWS的EC2, S3, RDS,Lambda具体简介
  • 2640. QYQ在艾泽拉斯
  • 基于 React + TypeScript + Fabric.js 构建一个封面生成器网站