
本文介绍两种有效方式:一是为类添加受保护构造函数以支持测试注入,二是使用Mockito验证日志行为;重点解决无法覆盖FileNotFoundException分支的测试难题。
本文介绍两种有效方式:一是为类添加受保护构造函数以支持测试注入,二是使用mockito验证日志行为;重点解决无法覆盖`filenotfoundexception`分支的测试难题。
在Java单元测试中,直接测试文件系统依赖(如FileInputStream)往往导致测试脆弱、不可靠且难以覆盖异常路径。原始代码中CRentaProperties构造器存在双重FileNotFoundException捕获逻辑——先尝试服务器路径,失败后回退至项目路径;若两者均不存在,则prop.load(input)将因input == null抛出NullPointerException(注意:原代码此处存在严重缺陷,未校验input是否为null),但更关键的是:标准JUnit无法天然模拟“文件不存在”这一外部状态。
✅ 推荐方案:可测试性重构 + Mockito验证
最实用且低侵入的改进方式是引入受保护的构造函数,将硬编码路径、Logger和Properties实例作为参数注入。这既保持了生产代码的向后兼容性(公有无参构造器仍可用),又为测试提供了可控入口:
// 修改后的 CRentaProperties 类(关键片段)
protected CRentaProperties(Logger log, Properties prop, String serverPath, String projectPath) {
InputStream input = null;
try {
input = new FileInputStream(serverPath); // 可控路径 → 触发 FileNotFoundException
} catch (FileNotFoundException e) {
log.error("ARCHIVO DE PROPIEDADES NO ENCONTRADO EN SERVIDOR", e);
try {
input = new FileInputStream(projectPath); // 再次可控路径
} catch (FileNotFoundException e1) {
log.error("ARCHIVO DE PROPIEDADES NO ENCONTRADO EN PROYECTO", e1);
// ⚠️ 重要:此处 input 仍为 null!后续 prop.load(input) 将 NPE
// 建议补充防御性检查:if (input == null) return;
}
}
try {
if (input != null) { // ✅ 补充空值校验,修复潜在NPE
prop.load(input);
}
} catch (IOException e) {
log.error("[ERROR DE PROPERTIES]", e);
} finally {
// ... 关闭逻辑(保持不变)
}
}对应测试用例使用Mockito精准验证异常处理逻辑是否被触发:
@ExtendWith(MockitoExtension.class)
class CRentaPropertiesTest {
@Mock Logger logger;
@Mock Properties properties;
@Test
void testMissingPropertiesFile() {
// 模拟两个路径均不存在 → 触发双层 catch
new CRentaProperties(logger, properties, "/invalid/server.conf", "/invalid/local.conf");
// 验证日志是否按预期记录两次错误
verify(logger).error(eq("ARCHIVO DE PROPIEDADES NO ENCONTRADO EN SERVIDOR"),
any(FileNotFoundException.class));
verify(logger).error(eq("ARCHIVO DE PROPIEDADES NO ENCONTRADO EN PROYECTO"),
any(FileNotFoundException.class));
// 确保 Properties 未被加载(因 input 为 null)
verifyNoInteractions(properties);
}
}⚠️ 注意事项与最佳实践
-
修复空指针隐患:原始代码在双重
catch后未检查input是否为null,直接调用prop.load(input)会导致NullPointerException。务必添加if (input != null)防御。 -
日志验证优先于行为断言:此类测试核心目标是确认异常路径的可观测行为(如日志输出),而非内部状态;Mockito的
verify()比assertEquals()更契合该目标。 - 避免真实I/O:切勿在测试中创建临时文件或依赖真实路径——这会破坏测试的隔离性与可移植性。
-
进阶建议(DI友好):若项目已使用Spring等框架,应进一步将
PropertyLoader抽象为接口,通过依赖注入实现彻底解耦,使测试仅需注入MockPropertyLoader即可。
通过以上重构,你不仅能100%覆盖FileNotFoundException分支,还能让测试稳定、快速、可读性强——这才是面向可维护性的专业测试实践。

















