Перейти к основному содержимому

Насмешка над файловой системой с помощью Jimfs

· 6 мин. чтения

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 содержит все, что нам нужно для использования нашей фиктивной файловой системы. Кроме того, мы будем писать тесты с использованием 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(Configuration.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 = "foreach";

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 , а не с File .

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

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

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

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