在使用Maven进行单元测试时,正确的写入临时文件的方法是什么?

80
我写了一个单元测试,它会将文件写入文件系统,如果没有指定路径,它会写入工作目录;所以如果从项目目录执行,它会写入项目根目录,如果在项目的父目录执行,它会写入父目录的根目录。
那么正确的写入目标目录的方法是什么?很可能是目标目录内的一个子目录吗?
如果我简单地指定文件为target/,它会将文件写入父项目的目标目录,而不是项目的目标目录。 更新:实际上,我希望在测试完成后得到该文件。该文件是为第三方提供的提取格式,需要发送给第三方。测试可以开关,以便只有在文件格式更改需要重新批准时才运行。文件去向并不是一个很大的问题,但我希望能够轻松找到它。
8个回答

84

你可以尝试使用TemporaryFolder JUnit @Rule,按照这里的描述。

TemporaryFolder会在默认的临时文件目录中创建一个文件夹,该目录由系统属性java.io.tmpdir指定。newFile方法在临时目录中创建一个新文件,而newFolder则创建一个新文件夹。

当测试方法完成时,JUnit会自动删除TemporaryFolder中所有文件和目录,包括其中的子目录和文件。无论测试是否通过,JUnit都会保证删除资源。


更新后的问题

你可以更改maven-surefire-plugin使用的工作目录。

<plugins>
    [...]
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.12.3</version>
        <configuration>
          <workingDirectory>${project.build.directory}</workingDirectory>
        </configuration>
      </plugin>
    [...]
</plugins>

你可以将工作目录更改为任何测试所需的目录,例如${project.build.directory}/my_special_dir/

surefire插件中的工作目录仅影响正在运行的测试,仅适用于由maven进行的测试。如果你在IDE中运行测试,则工作目录将是其他内容。


谢谢maba,这个问题和Bohemian的问题相似,文件最终会出现在一个鲜为人知的地方。我实际上希望测试完成后能得到这个文件。 - Brett Ryan
@BrettRyan 好的,已更新如何更改surefire插件的工作目录。那应该就可以了。 - maba
太棒了,谢谢maba,这很完美,无论在运行测试的所有机器上,始终如一。 - Brett Ryan
1
感谢您留下原始答案,这正是我所需要的。 - yellavon
@maba JUnit TemporaryFolder。请澄清上述评论。它似乎不能保证删除。 - Tarun Maganti
显示剩余2条评论

64

无需重复发明轮子...

JDK提供了创建临时文件的方法,并且提供了一种在退出时自动删除它的方式:

File file = File.createTempFile( "some-prefix", "some-ext");
file.deleteOnExit();

使用该文件,在测试结束时它会自动删除。就是这么简单。

要指定用于临时文件的目录,请使用重载的方法:

File file = File.createTempFile( "prefix", "ext", new File("/some/dir/path"));

1
谢谢Bohemian,实际上我希望文件在退出后仍然存在,这样我就可以将其发送给供应商。虽然我希望它在目标目录中,但我想系统临时目录也可以。 - Brett Ryan
1
我刚刚尝试了一下,结果发现文件在一个非常难找的地方,使用起来不明智。最终文件位于/var/folders/xf/scvh7yr11m1glr4b663n1hcsnv5lf0/T/test2614442099335581603.xml - Brett Ryan
4
没问题 - 你可以指定用于临时文件的目录 - 请参考编辑后的答案。 - Bohemian
1
谢谢Bohemian,我确实认为能够设置临时目录是有优点的,但我喜欢将其放在可靠位置的目标目录下,而不是需要在计算机上存在特定位置。 - Brett Ryan

5

自 JUnit 5 起,您可以使用 @TempDir。它将在测试之后被删除(包括其内容)。

或者为每个测试使用一个临时目录:

@Test
void testWithTempFiles(@TempDir Path tempDir) 
    Path file = tempDir.resolve("temp_file.txt");
    ...
}

或者一个用于类中所有测试的单一测试:

@TempDir
static Path tempDir

@Test
void testWithTempFiles() 
    Path file = tempDir.resolve("temp_file.txt");
    ...
}

这是目前最优雅和现代化的解决方案。 - Daniel Hári

2
事先声明,我强烈反对在单元测试中做这样的事情。我没有真正尝试过这个方法,但应该可以行得通:
假设您正在使用surefire插件,从文档中引用的内容可以通过System.getProperty("basedir")访问测试项目的基本目录。获取它并在basedir/target下创建文件。
更加"合适"的方法(因为我们可能会将输出目录配置为其他内容),您可以更改surefire插件配置为以下内容:
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.6</version>
    <configuration>
        <systemPropertyVariables>
            <myOutDir>${project.build.outputDirectory}</myOutDir>
        </systemPropertyVariables>
    </configuration>
</plugin>

然后你可以通过System.getProperty("myOutDir")在你的测试中获取实际的输出目录。


2
我会编写一个例程来确定文件应该写入的位置,并进行单元测试。一般情况下,我尽可能避免在单元测试中访问持久数据,如文件IO或数据库访问,这是出于性能和其他原因考虑。
请参考以下答案:https://dev59.com/znI_5IYBdhLWcg3wF_B3#8032504

谢谢O.D,直到现在我从未做过这样的事情。我编写了测试来编写一个文件,该文件将在生产代码中发送给其他供应商。我的测试将文件写入磁盘,以便我将其发送给供应商以获得批准,一旦获得批准,测试将不再需要,但在稍后需要“重新批准”输出时可能会有用。 - Brett Ryan
2
但是为什么你要使用“单元测试”来完成这样的任务呢?我认为,甚至拥有一个专门的带有main()方法的类来服务于这个目的比滥用单元测试更加合适。 - Adrian Shum

2
您会希望能够从IDE和Maven中运行测试,因此最佳实践是编写测试时不要假设它们正在Maven中运行。
处理临时文件的最佳方法之一是使用junit规则。这允许您依赖规则来为您清理工作。

1

临时文件应该被创建到临时目录中。使用调用System.getProperty("java.io.tmpdir")来检索它。

临时文件应该是临时的,即在不需要时应该被删除。为了实现这一点,在finally块或在测试后运行的代码中不要忘记删除它,即:

File tmpFile = ...
try {
   // deal with tempFile
} finally {
    tempFile.delete();
}

和/或

public class MyTestCase {
    private File tmpFile = null;

    @Before
    public void setUp() {
        tmpFile = ...;
    }

    @Test
    public void setUp() {
        // deal with tmpFile
    }

    @After
    public void setUp() {
        tmpFile.delete();
    }
}

如果可能的话,请使用File.createTempFile(String prefix, String suffix),这样您就可以使用由createTempFile()生成的特殊名称的文件。

请使用file.deleteOnExit()。这将创建一个钩子,在JVM终止时自动删除文件。如果使用kill -9杀死JVM,它将保留文件,因此没有机会运行关闭代码。


0
以下是使用JUnit 5和Kotlin语言的几种方法。
  • 在字段上使用JUnit 5的@TempDir

    @TempDir(cleanup = ON_SUCCESS)
    lateinit var tempDirectory: Path
    
    @Test fun example() {
        // 使用tempDirectory
    }
    
  • 在测试函数参数上使用JUnit 5的@TempDir

    @Test fun example(
        @TempDir(cleanup = CleanupMode.ON_SUCCESS) tempDirectory: Path
    ) {
        // 使用tempDirectory
    }
    
  • 手动创建文件并调用deleteOnExit()

    @Test fun example() {
        val file = File("file.txt").apply { deleteOnExit() }
    }
    
  • 在测试类或测试函数中使用Kotlin的createTempDirectorycreateTempFile函数:

    val file = createTempFile("file.txt")
    

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接