You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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的生成代码,改为:

  1. 过滤所有java.io包下AutoCloseable子类的变量引用(CtVariableReference)
  2. 检查该变量引用对应的变量声明,是否不在CtTryWithResource的资源列表里
  3. 排除合法的变量引用(即声明在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());
    }
}

修改说明

  1. 从CtTypeReference改为CtVariableReference:聚焦于变量的使用,而非类型本身的引用,避免把方法调用中的类型引用误判为违规。
  2. 检查变量声明的位置:通过varRef.getVariableDeclaration()获取变量的声明,再判断该声明是否属于CtTryWithResource的资源——如果不属于,才是违规的使用。
  3. 简化过滤逻辑:移除了不必要的ref.getPackage().isImplicit()判断,确保所有违规的变量引用都被捕获。

验证

运行修改后的测试,被测类中try-with-resources块内的bufferedWriter.write()和bufferedReader.readLine()操作会被正确识别为合法,不会再触发误判。

内容的提问来源于stack exchange,提问作者Николай Кукишев

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 15:09:57