Подигравка с файлова система с Jimfs

1. Общ преглед

Обикновено при тестване на компоненти, които използват интензивно I / O операции, нашите тестове могат да страдат от няколко проблема като лоша производителност, зависимост от платформата и неочаквано състояние.

В този урок ще разгледаме как можем да облекчим тези проблеми, използвайки файловата система Jimfs в паметта.

2. Въведение в Jimfs

Jimfs е файлова система в паметта, която реализира Java NIO API и поддържа почти всяка функция от нея. Това е особено полезно, тъй като означава, че можем да подражаваме на виртуална файлова система в паметта и да взаимодействаме с нея, използвайки съществуващия ни слой java.nio .

Както ще видим, може да е от полза да се използва подигравана файлова система вместо реална, за да:

  • Избягвайте да бъдете зависими от файловата система, която в момента изпълнява теста
  • Уверете се, че файловата система се сглобява с очакваното състояние при всяко тестване
  • Помогнете за ускоряване на нашите тестове

Тъй като файловите системи се различават значително, използването на Jimfs също улеснява лесното тестване с файлови системи от различни операционни системи.

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

Първо, нека добавим проектните зависимости, които ще са ни необходими за нашите примери:

 com.google.jimfs jimfs 1.1 

Зависимостта 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. Създаване на файл

В този раздел ще напишем тест, който тества метода за създаване от нашето хранилище:

@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 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. Тестове, зависими от операционната система

To demonstrate another benefit of using Jimfs, let's create a FilePathReader class. The class is responsible for returning the real system path, which is, of course, OS-dependent:

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

Now, let's add a test for this class:

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; } }

As we can see, the output for Windows differs from the one of Unix, as we'd expect. Moreover, we didn't have to run these tests using two different file systems — Jimfs mocked it for us automatically.

It's worth mentioning that Jimfs doesn't support the toFile() method that returns a java.io.File. It's the only method from the Path class that isn't supported. Therefore, it might be better to operate on an InputStream rather than a File.

7. Conclusion

In this article, we've learned how to use use the in-memory file system Jimfs to mock file system interactions from our unit tests.

First, we started by defining a simple file repository with several CRUD operations. Then we saw examples of how to test each of the methods using a different file system type. Finally, we saw an example of how we can use Jimfs to test OS-dependent file system handling.

Както винаги, кодът за тези примери е достъпен в Github.