ARTICLE DETAIL

资讯详情

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

Error Prone 的 CatchFail 检查器:消除测试中“吞掉异常再调 fail()“的反模式

Error Prone 的 CatchFail 检查器:消除测试中“吞掉异常再调 fail()“的反模式 静态分析代码质量开发工具【免费下载链接】error-proneCatch common Java mistakes as compile-time errors项目地址https://gitcode.com/gh_mirrors/er/error-prone点击查看免费下载导读本文讲解 Error Prone 内置检查器CatchFail所定位的一类典型 JUnit 测试反模式在catch块中忽略被捕获的异常、仅调用fail()让测试失败。这种写法不仅冗余未捕获的异常本身就会导致测试失败还会让异常信息与堆栈轨迹全部丢失严重削弱失败诊断能力。读完本文你将掌握该反模式的两种正确替代写法、CatchFail 的源码级匹配与自动修复逻辑、以及它在哪些场景下会刻意不提供修复。问题本质为什么 catch fail() 是多余的Error Prone 的 CatchFail 文档 明确指出该反模式的三个问题多余忽略异常并调用fail()毫无必要因为未捕获的异常本身就会让测试失败丢失诊断信息异常自带的消息message与堆栈轨迹stack trace在fail()之后全部丢失测试输出对排查毫无帮助掩盖真实原因失败时你只看到一句自定义文案看不到究竟抛出了什么异常、从哪里抛出。对应到源码CatchFail.java 中该检查器被注解为BugPattern( summary Ignoring exceptions and calling fail() is unnecessary, and makes test output less useful, severity WARNING) public class CatchFail extends BugChecker implements TryTreeMatcher {即它默认以WARNING级别SeverityLevel.WARNING报告属于 Error Prone 随编译器内置启用的一批检查器之一注册于 BuiltInCheckerSuppliers.java。推荐写法一不捕获异常让测试自然失败测试方法直接声明throws Exception被测代码一旦抛出异常JUnit 框架会自动将测试标记为失败异常消息与完整堆栈都会如实呈现在测试报告中Test public void testFoo() throws Exception { int x foos(); // the test fails if this throws assertThat(x).isEqualTo(42); }这种写法最简洁且保留了原始异常的完整信息是多数场景下的首选。推荐写法二捕获异常并用 AssertionError 包装当确实需要在失败时附加额外上下文比如解释这段业务逻辑为什么不该抛异常时可以把原始异常作为cause传给AssertionError既不丢堆栈又补充了说明信息Test public void testFoo() throws Exception { int x; try { x foos(); } catch (Exception e) { throw new AssertionError(the test failed, e); // wraps the exception with additional context } assertThat(x).isEqualTo(42); }注意AssertionError(Throwable)之外AssertionError(String message, Throwable cause)这一双参构造正是为附加上下文 保留根因设计的。反模式示例fail() 吞掉了异常信息下面的代码正是 CatchFail 要拦截的目标——catch块中只有一条fail(...)语句且完全没有使用捕获的异常变量eTest public void testFoo() { int x; try { x foos(); } catch (Exception e) { fail(the test failed); // the exception message and stack trace is lost } assertThat(x).isEqualTo(42); }失败时测试报告只显示 the test failed而真正重要的异常消息和堆栈轨迹已不可见。源码实现CatchFail 是如何识别的匹配条件在 CatchFail.java 的matchTry中检查器按以下条件筛选可疑的catch块该try语句至少有一个catch块tree.getCatches().isEmpty()时直接返回NO_MATCHcatch块只包含一条语句且这条语句是对fail()的表达式语句调用见FAIL_METHOD匹配器捕获的异常变量未被使用catchVariableIsUsed返回 false。FAIL_METHOD匹配器CatchFail.java覆盖了三种 JUnit 风格的fail静态方法兼容 JUnit 3 与 JUnit 4private static final MatcherStatementTree FAIL_METHOD expressionStatement( anyOf( staticMethod().onClass(org.junit.Assert).named(fail), staticMethod().onClass(junit.framework.Assert).named(fail), staticMethod().onClass(junit.framework.TestCase).named(fail)));关键约束是异常变量必须未被使用如果fail(oh no expected)之类的代码引用了异常变量说明开发者有意把异常信息拼进失败文案此时 CatchFail 不报告——CatchFailTest.useException 验证了这一负例expectUnchanged()。catch 变量是否被使用的检测catchVariableIsUsedCatchFail.java借助TreeScanner遍历整个catch块比较所有IdentifierTree的符号是否等于catch参数的VarSymbol一旦发现引用即标记为已使用。自动修复两条修复路径matchTry为每个命中的诊断同时尝试生成两种修复见 CatchFail.java。修复一重抛rethrowFixrethrowFixCatchFail.java只针对带消息参数的fail(message)调用将其改写为// fail(message) - throw new AssertionError(message, cause); throw new AssertionError(message, e);对于多参数格式串例如assertWithMessage(format %s, 42)风格的调用会先包装成String.format(...)再作为AssertionError的第一参数见getMessageOrFormatCatchFail.java。而无参的裸fail()不会被采用该修复——它没有额外上下文可保留直接删除更合理。对应的failVariations测试CatchFailTest.java展示了裸 fail 保留、带消息 fail 转 AssertionError的修复结果。修复二删除整个 catchdeleteFixdeleteFixCatchFail.java逻辑更复杂按结构分情况处理存在finally块或还有其它catch块时只删除多余的catch块catchBlocks.forEach(fix::delete)例如positive_otherCatch测试CatchFailTest.java中仅删除调用fail()的那个 catch保留另一个空 catchtry 块为空时整个try/catch都是死代码直接删除整个try语句且不修改方法签名对应 deleteEmptyTry 测试否则用try块内的语句替换整个try语句源码中特意通过整段区域替换来规避 Google Java Format 的局部重缩进 bug见 CatchFail.java 的注释。删除 catch 后原本被捕获的受检异常必须补进方法签名。deleteFix会汇总被删 catch 块中抛出的异常类型多 catch 用UnionClassType展开为各分支类型见 CatchFail.java过滤掉方法throws中已声明可赋值兼容的类型仅在方法确实是测试用例JUnitMatchers.TEST_CASE时才补throws声明否则放弃该修复——避免对普通方法做不安全的局部重构CatchFail.java。positive测试CatchFailTest.java完整展示了该修复Test方法与test前缀方法都被补上throws Exceptionpositive_failFailCatchFailTest.java则展示了多 catch 合并为throws Exception, Throwable的结果positive_finallyCatchFailTest.java验证了存在finally时保留 finally 且多 catch 类型各自进入throws的场景。刻意不修复的情况deleteFix对Test(expected ...)注解的方法直接放弃修复CatchFail.java这类测试依赖方法内抛出的特定异常把原始异常换成fail()或删除 catch 可能破坏其结构。expected测试CatchFailTest.java确认该方法源码保持不变。此外若没有找到外层方法比如代码位于 lambda 或初始化块中也不会生成修复CatchFail.java。测试视角CatchFail 的验证矩阵CatchFailTest.java 使用BugCheckerRefactoringTestHelper参见 BugCheckerRefactoringTestHelper.java做端到端验证覆盖了测试方法验证点positiveTest方法与非注解test前缀方法都触发检查并补throws Exceptionpositive_failFail多个命中 catch 合并throws声明Exception, Throwablepositive_finally存在finally时只删 catch、保留 finally多 catch 类型补进 throwspositive_otherCatch存在其它 catch 时只删除多余 catch 块negative_nonTest非测试方法触发诊断但无安全修复useExceptioncatch块使用了异常变量时不报告deleteEmptyTry空 try 整体删除、签名不动failVariations裸fail()保留、带消息的fail()转AssertionErrorexpectedTest(expected...)方法不采用删除修复使用方式与适用范围CatchFail 已作为内置检查器注册BuiltInCheckerSuppliers.java使用 Error Prone 编译器即可自动生效无需额外配置。它只针对 JUnit 3junit.framework.Assert/junit.framework.TestCase与 JUnit 4org.junit.Assert的fail静态方法若需要临时豁免可使用标准的SuppressWarnings(CatchFail)Error Prone 所有检查器默认支持SuppressWarnings抑制见 BugPattern.java 的suppressionAnnotations与disableable说明。小结把catch fail()改写为直接抛出或用AssertionError包装根因既简化了测试代码又保住了失败时最关键的异常堆栈——这正是 CatchFail 检查器存在的意义。赞分享静态分析代码质量开发工具【免费下载链接】error-proneCatch common Java mistakes as compile-time errors项目地址https://gitcode.com/gh_mirrors/er/error-prone点击查看免费下载相关推荐AutoDispose项目中的Error-Prone检查器使用指南AutoDispose项目中的Error Prone检查器使用指南 引言 在Android和RxJava开发中内存泄漏Memory Leak是一个常见且棘curl --show-error 参数全解析让 --silent 静默模式不再吞掉错误信息curl show error 参数全解析让 silent 静默模式不再吞掉错误信息 show error 短选项 S 是 curl 命令行工具中一个极其CLI网络通信上一篇英雄联盟回放管理神器ROFLPlayer让你的比赛复盘更简单下一篇100插件终极指南如何零代码打造专业级RPG游戏创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表