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

重构Java静态方法以提升可测试性(保留静态特性)

重构方案:保留静态特性并提升可测试性

核心思路是将方法内部直接创建URI和File实例的逻辑抽离为可替换的静态函数,测试时通过替换这些函数注入Mock对象,无需依赖PowerMock。

重构后的代码

import java.net.URI;
import java.net.URISyntaxException;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;

public class FilePathUtils {
    // 抽离URI创建逻辑,默认使用原生URI构造方法
    private static Function<String, URI> uriCreator = URI::new;
    // 抽离File创建逻辑,默认使用原生File(URI)构造方法
    private static Function<URI, File> fileCreator = File::new;

    // 测试专用:替换URI创建逻辑
    static void setUriCreator(Function<String, URI> creator) {
        uriCreator = creator;
    }

    // 测试专用:替换File创建逻辑
    static void setFileCreator(Function<URI, File> creator) {
        fileCreator = creator;
    }

    // 测试专用:重置为默认创建逻辑,避免测试污染
    static void resetCreators() {
        uriCreator = URI::new;
        fileCreator = File::new;
    }

    public static List<String> getAbsoluteFilePaths(String dirUrl) throws URISyntaxException {
        // 使用抽离的创建逻辑获取实例,而非直接new
        URI uri = uriCreator.apply(dirUrl);
        File folder = fileCreator.apply(uri);
        File[] fileList = folder.listFiles();
        List<String> fileNames = new ArrayList<>();

        if (fileList != null) {
            for (File file : fileList) {
                String fileOrDirUrl = uri.getScheme() + "://" +
                        (uri.getAuthority() != null ? uri.getAuthority() : "") + file.getAbsolutePath();

                if (file.isFile()) {
                    fileNames.add(fileOrDirUrl);
                } else if (file.isDirectory()) {
                    fileNames.addAll(getAbsoluteFilePaths(fileOrDirUrl));
                }
            }
        }
        return fileNames;
    }
}

测试示例(使用Mockito)

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.net.URI;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class FilePathUtilsTest {

    @BeforeEach
    void setUp() {
        // 每次测试前重置创建逻辑,避免测试间污染
        FilePathUtils.resetCreators();
    }

    @AfterEach
    void tearDown() {
        FilePathUtils.resetCreators();
    }

    @Test
    void getAbsoluteFilePaths_returnsAllFilePathsRecursively() throws Exception {
        // Mock根目录URI
        URI rootUri = mock(URI.class);
        when(rootUri.getScheme()).thenReturn("file");
        when(rootUri.getAuthority()).thenReturn(null);

        // Mock根文件夹
        File rootFolder = mock(File.class);
        // Mock根目录下的文件和子文件夹
        File mockFile = mock(File.class);
        when(mockFile.isFile()).thenReturn(true);
        when(mockFile.getAbsolutePath()).thenReturn("/test/docs/readme.txt");

        File mockSubDir = mock(File.class);
        when(mockSubDir.isDirectory()).thenReturn(true);
        when(mockSubDir.getAbsolutePath()).thenReturn("/test/docs/subdir");

        when(rootFolder.listFiles()).thenReturn(new File[]{mockFile, mockSubDir});

        // Mock子文件夹的URI和内容
        URI subDirUri = mock(URI.class);
        when(subDirUri.getScheme()).thenReturn("file");
        when(subDirUri.getAuthority()).thenReturn(null);

        File subDirFile = mock(File.class);
        when(subDirFile.isFile()).thenReturn(true);
        when(subDirFile.getAbsolutePath()).thenReturn("/test/docs/subdir/note.txt");
        when(mockSubDir.listFiles()).thenReturn(new File[]{subDirFile});

        // 替换创建逻辑,注入Mock对象
        FilePathUtils.setUriCreator(dirUrl -> {
            if ("file:///test/docs".equals(dirUrl)) {
                return rootUri;
            } else if ("file:///test/docs/subdir".equals(dirUrl)) {
                return subDirUri;
            }
            throw new IllegalArgumentException("Unexpected URI");
        });

        FilePathUtils.setFileCreator(uri -> {
            if (uri == rootUri) {
                return rootFolder;
            } else if (uri == subDirUri) {
                return mockSubDir;
            }
            throw new IllegalArgumentException("Unexpected URI");
        });

        // 执行测试方法
        List<String> result = FilePathUtils.getAbsoluteFilePaths("file:///test/docs");

        // 验证结果
        assertEquals(List.of(
                "file:///test/docs/readme.txt",
                "file:///test/docs/subdir/note.txt"
        ), result);
    }
}

方案优势

  • 完全保留原方法的静态特性,外部调用代码无需任何修改。
  • 无需PowerMock,Jacoco可正常统计测试覆盖率。
  • 通过静态的creator函数实现依赖注入,测试时灵活MockURI和File的行为。
  • 提供resetCreators方法,确保测试用例之间相互独立,避免污染。

内容的提问来源于stack exchange,提问作者Rahul Sahoo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 17:27:22