基于Spoon API的try-with-resources单元测试错误排查求助
问题分析与解决方案
你的测试误判try-with-resources块内的IO流操作,核心原因是错误地过滤了CtTypeReference(类型引用)而非CtVariableReference(变量引用),并且没有区分“变量声明是否在try-with-resources中”和“变量引用的位置”。
原逻辑的问题
你当前的代码会收集所有java.io包下AutoCloseable子类的类型引用,然后判断这些类型引用的父元素是否是TRY_RESOURCE角色。但在bufferedWriter.write()这类调用中,BufferedWriter类型的引用父元素是方法调用(CtInvocation),而非TRY_RESOURCE,所以被误判为违规。
我们真正需要检查的是:IO流变量的引用,其对应的变量声明是否不在try-with-resources的资源列表中——也就是那些在try块外声明、或者在普通try块内声明的IO流变量的使用。
修改后的测试逻辑
替换原测试中invalidRefs的生成代码,改为:
- 过滤所有
java.io包下AutoCloseable子类的变量引用(CtVariableReference) - 检查该变量引用对应的变量声明,是否不在
CtTryWithResource的资源列表里 - 排除合法的变量引用(即声明在try-with-resources中的变量的使用)
修改后的完整测试代码
@DisplayName("Test for non-functional requirements") public class ComplianceTest { private static final Set<String> ALLOWED_PACKAGES = Set.of( "java.lang", "java.util", "java.io", "java.time", "java.time.format", "edu.epam.fop.io" ); private static final Set<Class<?>> ALLOWED_CLASSES = Set.of( File.class, IOException.class, FileWriter.class, FileReader.class, BufferedWriter.class, BufferedReader.class ); @Test @SuppressWarnings({"unchecked", "varargs"}) void shouldUseTryWithResources() { var spoon = new Launcher(); spoon.addInputResource("src/main/java"); spoon.buildModel(); var factory = spoon.getFactory(); var model = spoon.getModel(); // 验证1:不得使用无资源的try语句 assertTrue(model .getElements(new CompositeFilter<>(FilteringOperator.SUBSTRACTION, new TypeFilter<>(CtTry.class), new TypeFilter<>(CtTryWithResource.class))) .isEmpty(), "No try without resources must be used"); // 验证2:必须使用try-with-resources处理IO assertFalse(model .getElements(new TypeFilter<>(CtTryWithResource.class)) .isEmpty(), "try-with-resources must be used to work with IO"); // 验证3:IO流不得在try-with-resources块外使用(修正后的逻辑) var autoCloseableIoType = factory.Class().createReference(AutoCloseable.class); var javaIoPackage = factory.Package().createReference("java.io"); var invalidRefs = model.getElements(new CompositeFilter<>(FilteringOperator.INTERSECTION, new TypeFilter<>(CtVariableReference.class), ref -> { CtTypeReference<?> varType = ref.getType(); // 过滤java.io下的AutoCloseable子类变量引用 return varType != null && !varType.isPrimitive() && varType.getTypeDeclaration() != null && varType.isSubtypeOf(autoCloseableIoType) && varType.getPackage().equals(javaIoPackage); } )).stream() .filter(varRef -> { // 获取变量声明 CtVariable<?> varDecl = varRef.getVariableDeclaration(); if (varDecl == null) { return false; // 排除无声明的引用(比如静态常量) } // 检查变量声明是否在try-with-resources的资源列表中 return varDecl.getParent(el -> el instanceof CtTryWithResource) == null; }) .toList(); if (!invalidRefs.isEmpty()) { var msg = invalidRefs.stream() .map(this::toErrorCodeReference) .collect(Collectors.joining("\n")); throw new AssertionError("Found usages of IO streams outside of try-with-resources:\n" + msg); } } private String toErrorCodeReference(CtElement element) { var pos = element.getPosition(); return String.format("%s.java:%d", pos.getCompilationUnit().getMainType().getQualifiedName(), pos.getLine()); } }
修改说明
- 从
CtTypeReference改为CtVariableReference:聚焦于变量的使用,而非类型本身的引用,避免把方法调用中的类型引用误判为违规。 - 检查变量声明的位置:通过
varRef.getVariableDeclaration()获取变量的声明,再判断该声明是否属于CtTryWithResource的资源——如果不属于,才是违规的使用。 - 简化过滤逻辑:移除了不必要的
ref.getPackage().isImplicit()判断,确保所有违规的变量引用都被捕获。
验证
运行修改后的测试,被测类中try-with-resources块内的bufferedWriter.write()和bufferedReader.readLine()操作会被正确识别为合法,不会再触发误判。
内容的提问来源于stack exchange,提问作者Николай Кукишев
相关产品推荐
相关产品推荐

