重构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
相关产品推荐
相关产品推荐

