«1. Обзор

Как правило, при тестировании компонентов, активно использующих операции ввода-вывода, наши тесты могут страдать от нескольких проблем, таких как низкая производительность, зависимость от платформы и неожиданное состояние.

В этом уроке мы рассмотрим, как мы можем облегчить эти проблемы, используя файловую систему в памяти Jimfs.

2. Знакомство с Jimfs

Jimfs — это файловая система в оперативной памяти, которая реализует Java NIO API и поддерживает почти все его функции. Это особенно полезно, поскольку означает, что мы можем эмулировать виртуальную файловую систему в памяти и взаимодействовать с ней, используя наш существующий слой java.nio.

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

    Избежать зависимости от файловой системы, в которой в данный момент выполняется тест. сборка с ожидаемым состоянием при каждом тестовом прогоне Помогите ускорить наши тесты

Поскольку файловые системы значительно различаются, использование Jimfs также облегчает тестирование с файловыми системами из разных операционных систем.

3. Зависимости Maven

Прежде всего, давайте добавим зависимости проекта, которые нам понадобятся для наших примеров:

<dependency>
    <groupId>com.google.jimfs</groupId>
    <artifactId>jimfs</artifactId>
    <version>1.1</version>
</dependency>

Зависимость jimfs содержит все, что нам нужно, чтобы использовать наш mocked файловая система. Кроме того, мы будем писать тесты с использованием JUnit5.

4. Простой файловый репозиторий

Мы начнем с определения простого класса FileRepository, который реализует некоторые стандартные операции CRUD:

public class FileRepository {

    void create(Path path, String fileName) {
        Path filePath = path.resolve(fileName);
        try {
            Files.createFile(filePath);
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }

    String read(Path path) {
        try {
            return new String(Files.readAllBytes(path));
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }

    String update(Path path, String newContent) {
        try {
            Files.write(path, newContent.getBytes());
            return newContent;
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }

    void delete(Path path) {
        try {
            Files.deleteIfExists(path);
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }
}

Как мы видим, каждый метод использует стандартный java.nio классы.

4.1. Создание файла

В этом разделе мы напишем тест, который проверяет метод create из нашего репозитория:

@Test
@DisplayName("Should create a file on a file system")
void givenUnixSystem_whenCreatingFile_thenCreatedInPath() {
    FileSystem fileSystem = Jimfs.newFileSystem(Configuration.unix());
    String fileName = "newFile.txt";
    Path pathToStore = fileSystem.getPath("");

    fileRepository.create(pathToStore, fileName);

    assertTrue(Files.exists(pathToStore.resolve(fileName)));
}

В этом примере мы использовали статический метод Jimfs.newFileSystem() для создания новая файловая система в памяти. Мы передаем объект конфигурации Configuration.unix(), который создает неизменяемую конфигурацию для файловой системы Unix. Сюда входит важная информация, относящаяся к ОС, такая как разделители путей и информация о символических ссылках.

Теперь, когда мы создали файл, мы можем проверить, был ли он успешно создан в системе на базе Unix.

4.2. Чтение файла

Далее мы протестируем метод, считывающий содержимое файла:

@Test
@DisplayName("Should read the content of the file")
void givenOSXSystem_whenReadingFile_thenContentIsReturned() throws Exception {
    FileSystem fileSystem = Jimfs.newFileSystem(Configuration.osX());
    Path resourceFilePath = fileSystem.getPath(RESOURCE_FILE_NAME);
    Files.copy(getResourceFilePath(), resourceFilePath);

    String content = fileRepository.read(resourceFilePath);

    assertEquals(FILE_CONTENT, content);
}

На этот раз мы проверили, возможно ли прочитать содержимое файла на macOS ( ранее OSX), просто используя другой тип конфигурации — Jimfs.newFileSystem(Configuration.osX()).

4.3. Обновление файла

Мы также можем использовать Jimfs для тестирования метода, который обновляет содержимое файла:

@Test
@DisplayName("Should update the content of the file")
void givenWindowsSystem_whenUpdatingFile_thenContentHasChanged() throws Exception {
    FileSystem fileSystem = Jimfs.newFileSystem(Configuration.windows());
    Path resourceFilePath = fileSystem.getPath(RESOURCE_FILE_NAME);
    Files.copy(getResourceFilePath(), resourceFilePath);
    String newContent = "I'm updating you.";

    String content = fileRepository.update(resourceFilePath, newContent);

    assertEquals(newContent, content);
    assertEquals(newContent, fileRepository.read(resourceFilePath));
}

Аналогичным образом, на этот раз мы проверили, как метод ведет себя в системе на базе Windows, используя Jimfs .newFileSystem(Конфигурация.windows()).

4.4. Удаление файла

Чтобы завершить тестирование наших операций CRUD, давайте протестируем метод, который удаляет файл:

@Test
@DisplayName("Should delete file")
void givenCurrentSystem_whenDeletingFile_thenFileHasBeenDeleted() throws Exception {
    FileSystem fileSystem = Jimfs.newFileSystem();
    Path resourceFilePath = fileSystem.getPath(RESOURCE_FILE_NAME);
    Files.copy(getResourceFilePath(), resourceFilePath);

    fileRepository.delete(resourceFilePath);

    assertFalse(Files.exists(resourceFilePath));
}

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

5. Перемещение файла

В этом разделе мы узнаем, как протестировать метод перемещения файла из одного каталога в другой.

Во-первых, давайте реализуем метод перемещения, используя стандартный класс java.nio.file.File:

void move(Path origin, Path destination) {
    try {
        Files.createDirectories(destination);
        Files.move(origin, destination, StandardCopyOption.REPLACE_EXISTING);
    } catch (IOException ex) {
        throw new UncheckedIOException(ex);
    }
}

Мы собираемся использовать параметризованный тест, чтобы убедиться, что этот метод работает в нескольких разных файловых системах: ~ ~~

private static Stream<Arguments> provideFileSystem() {
    return Stream.of(
            Arguments.of(Jimfs.newFileSystem(Configuration.unix())),
            Arguments.of(Jimfs.newFileSystem(Configuration.windows())),
            Arguments.of(Jimfs.newFileSystem(Configuration.osX())));
}

@ParameterizedTest
@DisplayName("Should move file to new destination")
@MethodSource("provideFileSystem")
void givenEachSystem_whenMovingFile_thenMovedToNewPath(FileSystem fileSystem) throws Exception {
    Path origin = fileSystem.getPath(RESOURCE_FILE_NAME);
    Files.copy(getResourceFilePath(), origin);
    Path destination = fileSystem.getPath("newDirectory", RESOURCE_FILE_NAME);

    fileManipulation.move(origin, destination);

    assertFalse(Files.exists(origin));
    assertTrue(Files.exists(destination));
}

Как мы видим, мы также смогли использовать Jimfs для проверки того, что мы можем перемещать файлы в различных файловых системах из одного модульного теста.

6. Тесты, зависящие от операционной системы

Чтобы продемонстрировать еще одно преимущество использования Jimfs, давайте создадим класс FilePathReader. Класс отвечает за возврат реального системного пути, который, конечно же, зависит от ОС:

class FilePathReader {

    String getSystemPath(Path path) {
        try {
            return path
              .toRealPath()
              .toString();
        } catch (IOException ex) {
            throw new UncheckedIOException(ex);
        }
    }
}

Теперь добавим тест для этого класса:

class FilePathReaderUnitTest {

    private static String DIRECTORY_NAME = "baeldung";

    private FilePathReader filePathReader = new FilePathReader();

    @Test
    @DisplayName("Should get path on windows")
    void givenWindowsSystem_shouldGetPath_thenReturnWindowsPath() throws Exception {
        FileSystem fileSystem = Jimfs.newFileSystem(Configuration.windows());
        Path path = getPathToFile(fileSystem);

        String stringPath = filePathReader.getSystemPath(path);

        assertEquals("C:\\work\\" + DIRECTORY_NAME, stringPath);
    }

    @Test
    @DisplayName("Should get path on unix")
    void givenUnixSystem_shouldGetPath_thenReturnUnixPath() throws Exception {
        FileSystem fileSystem = Jimfs.newFileSystem(Configuration.unix());
        Path path = getPathToFile(fileSystem);

        String stringPath = filePathReader.getSystemPath(path);

        assertEquals("/work/" + DIRECTORY_NAME, stringPath);
    }

    private Path getPathToFile(FileSystem fileSystem) throws Exception {
        Path path = fileSystem.getPath(DIRECTORY_NAME);
        Files.createDirectory(path);

        return path;
    }
}

Как мы видим, вывод для Windows отличается от Unix, как и следовало ожидать. Более того, нам не нужно было запускать эти тесты с использованием двух разных файловых систем — Jimfs автоматически имитировал это для нас.

«Стоит отметить, что Jimfs не поддерживает метод toFile(), возвращающий java.io.File. Это единственный неподдерживаемый метод класса Path. Поэтому может быть лучше работать с InputStream, а не с файлом.

7. Заключение

В этой статье мы узнали, как использовать файловую систему в памяти Jimfs для имитации взаимодействия с файловой системой из наших модульных тестов.

Во-первых, мы начали с определения простого репозитория файлов с несколькими операциями CRUD. Затем мы увидели примеры того, как тестировать каждый из методов с использованием другого типа файловой системы. Наконец, мы увидели пример того, как мы можем использовать Jimfs для тестирования обработки файловой системы, зависящей от ОС.

Как всегда, код этих примеров доступен на Github.