拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

JUnit 4.5 版本解析:BlockJUnit4ClassRunner 架构演进与 Theories 数据驱动增强

JUnit 4.5 版本解析:BlockJUnit4ClassRunner 架构演进与 Theories 数据驱动增强 测试开发工具【免费下载链接】junit4A programmer-oriented testing framework for Java — :warning: maintenance mode项目地址https://gitcode.com/gh_mirrors/ju/junit4点击查看免费下载JUnit 4.5Release Notes 见 doc/ReleaseNotes4.5.md是 JUnit 4 系列中承上启下的一次重要版本它首次引入基于Statement链式执行模型的BlockJUnit4ClassRunner并沉淀出ParentRunner、TestClass、RunnerBuilder等后续所有版本沿用至今的核心骨架。本文将逐条解析 4.5 的安装、运行、扩展、Theories 与开发工程化改进并结合当前仓库源码src/main/java、src/test/java验证每项变更的底层实现帮助读者理解 JUnit 4 执行模型的演进脉络并掌握自定义 Runner、扩展 Theories 的实战能力。一、安装方式junit-4.5.jar 与 junit-dep-4.5.jar4.5 版本同时发布两个 JAR 包区分标准在于是否内嵌 Hamcrest 匹配器库junit-4.5.jar包含运行 JUnit 所需的全部类含 Hamcrest 类适合大多数场景直接使用junit-dep-4.5.jar排除 Hamcrest 类专为已经在 JUnit 之外使用 Hamcrest 的开发者设计避免类路径上出现重复或版本冲突的 Hamcrest 定义。这一瘦身包思路在后续版本中延续当前仓库的 pom.xml 中对 Hamcrest 采用scopecompile/scope声明依赖并排除传递依赖使构建产物与 4.5 时代junit-dep的理念一脉相承。从仓库源码看Hamcrest 主要被org.junit.Assert.assertThatAssert.java与匹配器包 org/junit/matchers 使用若你的项目已有自己的 Hamcrest 版本选择 dep 包即可避免类冲突。二、基础运行体验改进1. JUnitCore 更准确的退出码命令行下运行org.junit.runner.JUnitCore时现在能更稳定地返回正确退出码成功为 0失败为 1。当前源码 JUnitCore.java 中main(String... args)直接调用System.exit(runMain(args))runMain的返回值由运行结果决定测试 JUnitCoreReturnsCorrectExitCodeTest.java 专门验证了该行为。这使得 CI 脚本可以依赖 JUnit 的退出码判断构建成败。2. 畸形测试类的报错信息更友好以下场景在 4.5 中会产生更清晰的初始化错误构造函数抛出异常类中没有测试方法存在多个构造函数Suite未提供SuiteClasses。这些校验逻辑在BlockJUnit4ClassRunner.collectInitializationErrors()BlockJUnit4ClassRunner.java与ParentRunner.validate()ParentRunner.java中集中实现例如validateOnlyOneConstructor报出Test class should have exactly one public constructorvalidateZeroArgConstructor报出Test class should have exactly one public zero-argument constructor非静态内部类会被拒绝The inner class ... is not static.。对应的回归测试可见 BadlyFormedClassesTest.java 与 FailedConstructionTest.java。3. 只含继承测试方法的类可以运行4.5 之前若一个测试类的所有Test方法都定义在父类中子类可能被错误判定为无测试。4.5 起这类类可以正常执行。其根源在于TestClass在收集注解方法时会遍历父类BlockJUnit4ClassRunner.computeTestMethods()通过getTestClass().getAnnotatedMethods(Test.class)取到本类与父类中所有非覆写的Test方法BlockJUnit4ClassRunner.java相关测试见 InheritedTestTest.java。4. 注解处理优化开销减半4.5 对注解扫描做了优化在大型测试类上可将 JUnit 自身开销削减一半以上尤其对 Theories 场景收益明显对应 Bug 1796847。其工程基础正是本版本新增的TestClass缓存机制——它将按注解查找方法的结果缓存起来避免每次执行都重新反射扫描详见下文第四节。5. 构造函数中的失败假设会忽略整个类Assume假设Assumption此前通常出现在测试方法体内4.5 起若假设失败发生在构造函数中整个类会被视为忽略而不是报错。当前ParentRunner.run()在捕获AssumptionViolatedException时会走addFailedAssumption分支ParentRunner.java确保假设失败被当作跳过而非失败处理。配合 Assume.java 使用可在环境不满足前提条件时静默跳过整套测试。6. 字符串 null 与 null 值的比较结果修正修复了 Bug 1857283assertEquals(null, null)这类比较在 4.5 中结果正确。同时AssumptionViolatedException.toString()的输出也变得更具有信息量便于在日志中定位假设失败原因。这类相等性断言的实现集中在 Assert.java 的assertEquals族方法与 ComparisonFailure.java 的格式化逻辑中。7.RunWith(JUnit4.class)的默认 Runner 变化4.5 起RunWith(JUnit4.class)始终调用当前版本 JUnit 的默认 Runner4.4 时代是JUnit4ClassRunner4.5 起切换为BlockJUnit4ClassRunner且官方明确提示未来可能再次变化。因此不应依赖默认 Runner 的具体类型而应把RunWith(JUnit4.class)视为使用 JUnit 4 标准行为的语义声明。当前仓库中 JUnit4.java 即为该注解对应的 Runner 别名。三、核心架构演进BlockJUnit4ClassRunner 与 Statement 链4.5 最大的架构事件是新的标准测试类 Runner 登场。注意 Release Notes 中写作BlockJUnit4Runner仓库实际类名为BlockJUnit4ClassRunnerBlockJUnit4ClassRunner.javasince 4.5的 Javadoc 印证了其出身。它与旧实现JUnit4ClassRunner位于 internal 包、现已弃用相比有两大根本差异1. 基于 Statement 的简单实现可在执行流任意点插入新操作StatementStatement.java是一个只含evaluate()的抽象代表一段可执行逻辑。每个测试方法的执行被拆成多层 Statement 嵌套每一层负责一个横切关注点。BlockJUnit4ClassRunner.methodBlock()BlockJUnit4ClassRunner.java按顺序包裹出完整链条Statement statement methodInvoker(method, test); // 真正反射调用 Test statement possiblyExpectingExceptions(method, test, statement); // 支持 Test(expected...) statement withPotentialTimeout(method, test, statement); // 支持 Test(timeout...) statement withBefores(method, test, statement); // 执行 Before statement withAfters(method, test, statement); // 执行 After statement withRules(method, test, statement); // 应用 Rule statement withInterruptIsolation(statement); // 隔离线程中断状态每个withXxx方法都是protected的独立钩子子类可以只覆写其中某一层把自定义逻辑精确插入到执行流程的指定位置——这就是Block块命名的由来。底层实现上possiblyExpectingExceptions返回ExpectException、超时返回FailOnTimeout、前置/后置方法分别由RunBefores/RunAftersstatements 包承担规则应用则由RuleContainerRuleContainer.java统一调度。各层职责的回归测试可见 BlockJUnit4ClassRunnerTest.java 与 CustomBlockJUnit4ClassRunnerTest.java。2. 公开可扩展取代 internal 包中的旧 RunnerJUnit4ClassRunner位于org.junit.internal.runners内部包不鼓励外部复用BlockJUnit4ClassRunner则位于公开的org.junit.runners包官方明确鼓励继承与复用。从源码看其扩展点覆盖了从计算测试方法列表computeTestMethods到构建每个 StatementmethodInvoker/withBefores/withAfters/withRules等的完整生命周期子类可按需覆写任意环节。四、ParentRunner 与 TestClassRunner 家族的公共基座ParentRunner面向子对象迭代的 Runner 基类ParentRunnerTParentRunner.java是会迭代一组 children 的 Runner的基类每个 child 代表一个测试或子 Suite。它向下层提供了四类开箱即用的能力类级生命周期classBlock()ParentRunner.java把 children 的调用包进BeforeClass、AfterClass、类级ClassRule与中断隔离的外层 StatementwithBeforeClasses/withAfterClassesL237-L256分别收集父类与本类上的BeforeClass/AfterClass方法其中AfterClass即使前置步骤抛异常也保证执行并把多个异常合并为MultipleFailureException过滤与排序实现Filterable与Sortable接口filter(Filter)L429-L452按Filter.shouldRun剔除 child全部被剔除时抛出NoTestsRemainExceptionsort(Sorter)L454-L470对 children 排序两者都通过childrenLock保证并发安全方法校验collectInitializationErrors与validatePublicVoidNoArgMethods对BeforeClass/AfterClass做public、void、无参、静态的合法性检查调度runChildren通过可替换的RunnerScheduler调度每个 child 的执行默认串行也为后续版本的并行执行预留了接口。TestClass注解方法的缓存式访问TestClassTestClass.java把要运行的类包装起来提供对带某注解的所有方法/字段的高效、可重复访问。其实现会在首次查询后缓存结果这正是 Release Notes 中注解处理优化使 JUnit 开销减半的直接来源——大型测试类不再需要为每次方法查询重复反射遍历。getAnnotatedMethods(Class? extends Annotation)、collectAnnotatedFieldValues、collectAnnotatedMethodValues等 API 被BlockJUnit4ClassRunner与ParentRunner大量使用成为整个 Runner 框架的数据中枢。五、RunnerBuilder 扩展 API 与新的 Enclosed 运行器RunnerBuilderSuite 类自定义 Runner 的扩展点新增的RunnerBuilderAPIRunnerBuilder.java允许自定义 Suite 类扩展如何为每个子类构建 Runner的行为。例如 Suite.java 构造时接收RunnerBuilder把为每个SuiteClasses子类选择 Runner的逻辑委托给 builderAnnotatedBuilder、JUnit3Builder、JUnit4Builder、IgnoreClassRunner等builders 包构成了默认的 builder 链自定义 Runner 通过向链中注入自己的 builder 即可参与 Suite 的子类调度。Enclosed运行外部类的全部静态内部类新 RunnerEnclosedEnclosed.java继承自Suite其构造函数通过klass.getClasses()取出外部类的全部成员类并用filterAbstractClasses过滤掉抽象类后交给RunnerBuilder逐个构建 Runner——即运行一个外部类时自动运行其所有静态内部测试类非常适合把同一主题的测试分组放在一个外部类中的组织风格。相应测试见 EnclosedTest.java。移除 Parameterized.eachOne()Parameterized.eachOne()这个旧的参数化辅助方法在 4.5 中被移除参数化测试的用法统一收敛到RunWith(Parameterized.class)Parameters的官方模式上。六、Theories数据驱动测试的显著增强Theories 在 4.5 中获得了多项实质改进源码集中在 org/junit/experimental/theories 包。1. Before/After 按每组参数执行且每组参数使用新实例语义变更非常关键Before与After现在会在每一组尝试的参数组合前后执行并且每组参数都在测试类的新实例上运行。这保证了对不同数据点组合的隔离——前一组参数对实例状态的污染不会泄漏到下一组。实现上Theories.java 的自定义 runner 在runWithIncompleteAssignmentL213 附近中为每组参数组装Assignments并执行 Statement每组参数都通过createTest构造新实例再套用BlockJUnit4ClassRunner.methodBlock的标准 Statement 链含Before/After执行。2. 暴露 ParameterSignature.getType() 与 getAnnotations()ParameterSignatureParameterSignature.java现在公开了两个查询 APIgetType()返回参数的类型Class?用于数据点与参数的类型匹配getAnnotations()返回参数上的全部注解列表供自定义ParameterSupplier读取参数级配置。这使得编写基于参数注解的数据源如 TestedOnSupplier.java成为可能是ParametersSuppliedByParametersSuppliedBy.java机制的关键支撑。3. 新增 DataPoints以数组形式注入一组数据点4.5 新增DataPoints注解DataPoints.java允许用一个字段或方法一次性引入一整套数据点其值可以是数组或 Iterable与单值形式的DataPoint互补。注解目标为字段与方法Target({ FIELD, METHOD })运行期保留Retention(RUNTIME)。用法示例public class DateFormatTheory { DataPoints public static String[] goodDates { 2008/04/07, 2008/04/8, 08/04/07 }; DataPoints public static String[] badDates() { return new String[] { 2008/04/07/ }; } Theory public void parseableDates(String date) { assertNotNull(DateFormat.getInstance().parse(date)); } }对应测试见 WithDataPointMethod.java 与 WithNamedDataPoints.java后者验证FromDataPoints按名字选用指定数据点集合该能力在后续版本中正式化见 FromDataPoints.java。4. Theories runner 重构更快、更易扩展Theories 自定义 runner 整体重构目的有二提升数据点组合的匹配效率配合TestClass缓存大型数据点集性能明显改善以及让扩展点更清晰。数据点收集与组合的职责被拆分到 AllMembersSupplier.java、SpecificDataPointsSupplier.java 与 Assignments.java 中自定义数据源只需实现ParameterSupplierParameterSupplier.java即可接入。相关测试覆盖可见 AllMembersSupplierTest.java 与 TheoriesPerformanceTest.java。七、开发与工程化改进源码目录拆分代码从单一目录拆为src/main/java与src/test/java即当前仓库的布局 src/main/java 与 src/test/java构建时可轻松排除测试源码也更符合 Maven 约定为后续版本的 Maven 化打下基础测试子包组织org.junit.tests下的测试类按主题拆分为assertion、description、junit3compatibility、listening、manipulation、running、validation、experimental等子包src/test/java/org/junit/tests显著降低查找特定测试的成本ResultMatchers 更详细的描述ResultMatchers.java 生成的失败描述更具可读性便于与 Hamcrest 配合断言运行结果测试见 ResultMatchersTest.javaTestSystem 抽象新增TestSystemsrc/test/java/org/junit/tests/TestSystem.java允许测试代码在无真实系统环境的情况下验证退出码等系统级交互JUnitCore.runMain(JUnitSystem, ...)正是面向该抽象的入口JUnitCore.java。八、不兼容变更与迁移建议4.5 引入以下破坏性变更升级时需注意移除Request.classes(String, Class?...)工厂方法旧的字符串参数形式被移除请改用Request.aClass(Class?)、Request.classes(Class?...)等基于 Class 对象的工厂方法当前 Request.java 中即以此类 API 为准移除Parameterized.eachOne()改用标准RunWith(Parameterized.class)Parameters模式JUnit4ClassRunner弃用internal 包中的旧实现被弃用新代码应基于BlockJUnit4ClassRunner或直接使用默认 RunnerTheories 语义调整Before/After改为按每组参数执行且每组使用新实例依赖旧语义方法级共享实例状态的 Theories 测试需要适配。小结JUnit 4.5 的价值不在于新增了多少注解而在于把执行模型从黑盒 Runner重构为可组合的 Statement 链BlockJUnit4ClassRunner成为标准测试类的执行引擎ParentRunner与TestClass成为 Suite、Parameterized、Theories 等所有 Runner 的共同基座RunnerBuilder则打通了自定义 Runner 的装配路径。理解了 4.5 的这些设计再看 4.6 之后的Rule、ClassRule、Category、FixMethodOrder等特性都能在同一个 Statement 组合模型中找到落点。本仓库的 ReleaseNotes 系列文档 与 源码 是继续深入这份演进史的最佳材料。赞分享测试开发工具【免费下载链接】junit4A programmer-oriented testing framework for Java — :warning: maintenance mode项目地址https://gitcode.com/gh_mirrors/ju/junit4点击查看免费下载相关推荐IGListKit 版本演进全解析从 CHANGELOG 读懂数据驱动 UICollectionView 框架的架构变迁IGListKit 版本演进全解析从 CHANGELOG 读懂数据驱动 UICollectionView 框架的架构变迁 IGListKit 是一个由 Met移动开发UI组件Slate 0.62 至 0.126 版本演进全解析架构重构、性能优化与扩展性增强Slate 0.62 至 0.126 版本演进全解析架构重构、性能优化与扩展性增强 本指南以官方 packages/slate/CHANGELOG.md ht前端富文本UI组件Anu框架版本演进与技术架构解析Anu框架版本演进与技术架构解析 概述 Anu是一个轻量级的React16兼容框架由国内开发者RubyLouvre创建。它旨在提供与React16完全兼容的A上一篇TensorFlow Data Validation 数据统计计算实战从基础统计到高级分析下一篇终极指南使用Git版本控制管理backtesting.py策略迭代与回测结果创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表