1. 概述

在编写单元测试时,我们经常需要使用临时文件。但手动创建、管理以及删除这些文件会带来一定负担。

JUnit 5 自 5.4.2 版本起引入了 TempDirectory 扩展,为我们提供了一种简单、统一的方式来处理临时目录的创建与清理。本文将带你快速了解如何在 JUnit 5 中使用 @TempDir 注解来简化临时文件操作。

2. TempDirectory 扩展简介

JUnit 5 提供的 TempDirectory 扩展是一个内置的、默认注册的扩展组件,无需额外使用 @ExtendWith(TempDirectory.class) 注解即可使用。

它支持在测试方法或测试类级别创建临时目录,并在测试结束后自动清理。需要注意的是,该功能在 JUnit 5 中仍处于 实验性(EXPERIMENTAL) 阶段,鼓励用户反馈使用体验。

3. Maven 依赖配置

在使用 TempDirectory 功能前,需要引入以下 JUnit 5 的 Maven 依赖:

JUnit 核心 API:

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>5.10.2</version>
    <scope>test</scope>
</dependency>

参数化测试依赖(可选):

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-params</artifactId>
    <version>5.10.2</version>
    <scope>test</scope>
</dependency>

最新版本可在 Maven Central 查找。

4. 使用 @TempDir 注解

JUnit 5 提供了 @TempDir 注解用于注入临时目录。目前只支持两种类型:

  • java.nio.file.Path
  • java.io.File

如果使用其他类型会抛出 ParameterResolutionException

下面我们来看几种典型的使用方式。

4.1. 作为测试方法参数

你可以将 @TempDir 用作测试方法的参数,JUnit 会自动注入一个临时目录:

@Test
void givenTestMethodWithTempDirectory_whenWriteToFile_thenContentIsCorrect(@TempDir Path tempDir) 
  throws IOException {
    Path numbers = tempDir.resolve("numbers.txt");

    List<String> lines = Arrays.asList("1", "2", "3");
    Files.write(numbers, lines);

    assertAll(
      () -> assertTrue("File should exist", Files.exists(numbers)),
      () -> assertLinesMatch(lines, Files.readAllLines(numbers)));
}

✅ 这个测试方法会创建一个临时文件并写入内容,然后验证文件是否存在以及内容是否正确。

4.2. 作为类成员变量

你也可以将 @TempDir 注解用于类的字段,JUnit 会在整个测试类中为其创建临时目录:

@TempDir
File anotherTempDir;

@Test
void givenFieldWithTempDirectoryFile_whenWriteToFile_thenContentIsCorrect() throws IOException {
    assertTrue("Should be a directory", this.anotherTempDir.isDirectory());

    File letters = new File(anotherTempDir, "letters.txt");
    List<String> lines = Arrays.asList("x", "y", "z");

    Files.write(letters.toPath(), lines);

    assertAll(
      () -> assertTrue("File should exist", Files.exists(letters.toPath())),
      () -> assertLinesMatch(lines, Files.readAllLines(letters.toPath())));
}

⚠️ 注意:如果多个测试方法共用同一个字段,每个测试方法都会获得一个独立的临时目录实例

4.3. 共享临时目录

如果你希望多个测试方法共享同一个临时目录,可以将字段声明为 static

@TempDir
static Path sharedTempDir;

@Test
@Order(1)
void givenFieldWithSharedTempDirectoryPath_whenWriteToFile_thenContentIsCorrect() throws IOException {
    Path numbers = sharedTempDir.resolve("numbers.txt");

    List<String> lines = Arrays.asList("1", "2", "3");
    Files.write(numbers, lines);

    assertAll(
        () -> assertTrue("File should exist", Files.exists(numbers)),
        () -> assertLinesMatch(lines, Files.readAllLines(numbers)));
}

@Test
@Order(2)
void givenAlreadyWrittenToSharedFile_whenCheckContents_thenContentIsCorrect() throws IOException {
    Path numbers = sharedTempDir.resolve("numbers.txt");

    assertLinesMatch(Arrays.asList("1", "2", "3"), Files.readAllLines(numbers));
}

✅ 使用 @Order 可以控制测试方法的执行顺序,确保测试逻辑顺序执行。

4.4. 控制目录清理行为

JUnit 默认会在测试方法执行完成后自动删除临时目录。但你可以通过 cleanup 参数控制清理行为:

@Test
@Order(1)
void whenTestMethodWithTempDirNeverCleanup_thenSetInstanceVariable(@TempDir(cleanup = NEVER) Path tempDir) {
    theTempDirToBeChecked = tempDir;
    System.out.println(tempDir.toFile().getAbsolutePath());
}

@Test
@Order(2)
void whenTestMethodWithTempDirNeverCleanup_thenTempDirShouldNotBeRemoved() {
    assertNotNull(theTempDirToBeChecked);
    assertTrue(theTempDirToBeChecked.toFile().isDirectory());
}

清理模式包括:

  • ALWAYS:总是清理(默认)
  • ON_SUCCESS:仅在测试成功时清理
  • NEVER:从不清理

⚠️ 使用 NEVER 模式时,需配合 @TestInstance(PER_CLASS) 使用,否则实例字段在后续测试中不可用。

5. 使用细节与注意事项

5.1. 临时目录创建位置

JUnit 使用 Files.createTempDirectory(String prefix) 创建临时目录,默认使用系统临时目录(由环境变量 TMPDIR 指定)。

例如:

TMPDIR=/var/folders/3b/rp7016xn6fz9g0yf5_nj71m00000gn/T/

生成的文件路径类似:

/var/folders/3b/rp7016xn6fz9g0yf5_nj71m00000gn/T/junit5416670701666180307/numbers.txt

如果创建失败,会抛出 ExtensionConfigurationExceptionParameterResolutionException

5.2. 清理失败处理

默认情况下,JUnit 会在测试结束后递归删除临时目录及其内容。如果删除失败,会抛出 IOException 并导致测试失败。

6. 自定义临时目录创建逻辑

如需自定义临时目录的创建逻辑,可以实现 TempDirFactory 接口:

class TempDirFactoryUnitTest {

    @Test
    void factoryTest(@TempDir(factory = Factory.class) Path tempDir) {
        assertTrue(tempDir.getFileName().toString().startsWith("factoryTest"));
    }

    static class Factory implements TempDirFactory {

        @Override
        public Path createTempDirectory(AnnotatedElementContext elementContext, ExtensionContext extensionContext)
                throws IOException {
            return Files.createTempDirectory(extensionContext.getRequiredTestMethod().getName());
        }

    }
}

✅ 通过实现 createTempDirectory 方法,你可以完全控制临时目录的命名与创建逻辑。

7. 总结

JUnit 5 的 @TempDir 注解为我们提供了一种优雅、简洁的方式来管理测试中的临时文件和目录。它支持多种使用方式,包括:

  • 方法参数注入
  • 类字段注入
  • 多方法共享目录
  • 自定义清理策略
  • 自定义创建逻辑

使用时需注意:

  • 支持类型只有 PathFile
  • @TempDir 是实验性功能,未来可能有变化
  • 清理失败会导致测试失败
  • 可通过 TempDirFactory 实现自定义逻辑

完整示例代码可在 GitHub 获取。


原始标题:JUnit 5 Temporary Directory Support