Базовый обзор @TempDir в Java

от автора

Привет, Хабр!

Сегодня разберемся с @TempDir — мощным, но часто недооценённым инструментом JUnit 5 для работы с временными файлами и директориями в тестах.

Зачем вообще нужны временные каталоги?

Тесты, которые пишут на диск, имеют неприятное свойство:

  1. Засоряют /tmp, если вы забыли подчистить.

  2. Ломаются в CI, когда два воркера пытаются писать в один и тот же файл.

  3. Падают на Windows, потому что «файл используется другим процессом».

@TempDir решает все три проблемы: JUnit создает уникальную папку, инжектирует вам Path/File, а потом — по дефолту вычищает за собой.

class ReportServiceTest {      @Test     void generatesCsv(@TempDir Path temp) throws IOException {         Path report = temp.resolve("users.csv");         new ReportService().writeCsv(report);          assertLinesMatch(             List.of("id,name", "1,Alice", "2,Bob"),             Files.readAllLines(report)         );     } }

Секундное дело: получили каталог, передали его в прод‑код, проверили результат, забыли. JUnit сам удалит папку после теста.

Три лица cleanup: ALWAYS, ON_SUCCESS, NEVER

У @TempDir есть параметр cleanup, который понимает три значения:

Режим

Что делает

Когда нужен

ALWAYS (дефолт)

чистит всегда

99% юз‑кейсов

ON_SUCCESS

чистит только при зеленом тесте

дебаг фейлов, flaky‑ад

NEVER

ничего не чистит

редкие интеграционные сценарии

@Test void debugFailure(         @TempDir(cleanup = ON_SUCCESS) Path temp ) throws Exception {     // ... }

Провалился ассерт — папка осталась, можно руками залезть и посмотреть артефакты.

Кстати, лобально настроить режим можно в junit-platform.properties
junit.jupiter.tempdir.cleanup.mode.default=ON_SUCCESS

С JUnit 5.11+ ON_SUCCESS наконец учитывает nested‑тесты и class‑level @TempDir.

CSV-репорты

Допустим, есть сервис, который принимает список пользователей и складывает отчет в Path.

public final class ReportService {      public void writeCsv(Path target) {         try (BufferedWriter w = Files.newBufferedWriter(target)) {             w.write("id,name\n");             users().forEach(u -> IoUtil.safeWrite(w, "%d,%s\n", u.id(), u.name()));         } catch (IOException e) {             throw new UncheckedIOException(e);         }     }      /* … business logic … */ }

Тест с @TempDir — элементарен (см. первый пример). Не нужно париться о коллизиях имен: каталог уникальный.

Тестируем код, который пишет в OutputStream

Когда прод‑код получает OutputStream, еще проще: подсовываем ByteArrayOutputStream.

@Test void writesXmlToStream() {     ByteArrayOutputStream out = new ByteArrayOutputStream();     new XmlWriter(out).writeString("foo");      assertThat(out.toString(UTF_8)).isEqualTo("<tag>foo</tag>"); }

Без диска, без прав на файлы, тест бежит в микросекунды.

Jimfs — in-memory FS как тестовый дабл

Но как быть, если код жестко завязан на Path и дергает тонну Files.*? В бой пускаем [Jimfs] — файловую систему в памяти.

FileSystem fs = Jimfs.newFileSystem(Configuration.unix()); Path tmp = fs.getPath("/tmp");           // mkdir -p /tmp Files.createDirectories(tmp); new ReportService().writeCsv(tmp.resolve("users.csv"));

С JUnit 5.10 появился SPI TempDirFactory. Пишем фабрику:

class JimfsTempDirFactory implements TempDirFactory {     private final FileSystem fs = Jimfs.newFileSystem(Configuration.unix());      @Override     public Path createTempDirectory(Object context) throws IOException {         return Files.createTempDirectory(fs.getPath("/"), "junit");     } }

И используем:

@Test void reportInMemory(         @TempDir(factory = JimfsTempDirFactory.class) Path dir ) { /* ... */ }

Теперь тесты не трогают реальную FS вообще.

Можно прописать junit.jupiter.tempdir.factory.default=...JimfsTempDirFactory, и все @TempDir в проекте автоматически будут Jimfs‑овые.

Прочие моменты

Symlink‑ловушки. JUnit 5.12+ предупреждает, если в tmp‑папке есть симлинк наружу: ссылка удаляется, цель — нет. Безопаснее, чем тупое rm -rf.

Параллельные тесты. Каждый @TempDir уникальный — можно смело включать junit.jupiter.execution.parallel.enabled=true.

Windows‑file‑locking. Не держите FileChannel открытым дольше, чем нужно. Даже @TempDir тут бессилен.

Когда Jimfs не подходит

  • Тестируете логику, завязанную на ACL/xattrs.

  • Нужно проверить work‑with‑network‑share.

  • Используете JNI‑вызывающую fchmod() — Jimfs это не эмулирует.

В этих случаях берите настоящий диск + @TempDir(cleanup = NEVER) и подчищайте руками в @AfterEach.


Если вы все еще вручную создаёте временные файлы в тестах, крутите deleteOnExit() и надеетесь, что CI сам как‑нибудь разберётся — пора остановиться. @TempDir в JUnit закрывает весь этот зоопарк одним аннотационным выстрелом: уникальные директории, гарантированный cleanup, встроенная поддержка in‑memory файловых систем, а с 5.10+ — еще и тонкая настройка поведения через фабрики.

Не забывайте про cleanup = ON_SUCCESS, когда отлаживаете сложные фейлы, и обязательно держите JUnit в актуальной версии: начиная с 5.11–5.12, пофикшены баги с nested‑тестами, символическими ссылками и ранним удалением директорий. Также имеет смысл завести свою TempDirFactory. Потратите один вечер — сэкономите сотни в будущем.

В целом, любой код, взаимодействующий с файловой системой, должен быть изолирован в тестах. @TempDir создает, передает, чистит. Все, что остается вам — писать логику, не думая о побочных эффектах.


Если вы всерьёз работаете с файловой системой в Java — с вас никто не требует помнить наизусть все особенности @TempDir, Jimfs, FileChannel и прочих тонкостей. Но понимать, где и как их применять, — уже почти обязательный минимум для современного инженера.

Если вы хотите разобраться глубже, укрепить архитектуру и писать тесты, которые не ломаются на CI, — загляните в наш курс «Java Developer. Basic». Программа основана на реальных кейсах, преподаватели — практики из индустрии.

Кроме того, у нас открыт каталог курсов OTUS, где вы можете подобрать направление под свой стек и задачи.

Также рекомендуем заглянуть в календарь открытых уроков. Там всегда можно найти что‑то полезное и актуальное — и бесплатно.


ссылка на оригинал статьи https://habr.com/ru/articles/920200/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *