Dateisystem verspottet mit Jimfs

1. Übersicht

Beim Testen von Komponenten, die E / A-Vorgänge stark beanspruchen, können unsere Tests in der Regel unter verschiedenen Problemen leiden, z. B. schlechter Leistung, Plattformabhängigkeit und unerwartetem Status.

In diesem Tutorial sehen wir uns an, wie wir diese Probleme mithilfe des speicherinternen Dateisystems Jimfs beheben können.

2. Einführung in Jimfs

Jimfs ist ein In-Memory-Dateisystem, das die Java NIO-API implementiert und nahezu alle Funktionen unterstützt. Dies ist besonders nützlich, da wir damit ein virtuelles In-Memory-Dateisystem emulieren und mit unserer vorhandenen java.nio- Ebene damit interagieren können .

Wie wir sehen werden, kann es vorteilhaft sein, ein verspottetes Dateisystem anstelle eines echten zu verwenden, um:

  • Vermeiden Sie es, von dem Dateisystem abhängig zu sein, in dem der Test gerade ausgeführt wird
  • Stellen Sie sicher, dass das Dateisystem bei jedem Testlauf mit dem erwarteten Status zusammengestellt wird
  • Helfen Sie dabei, unsere Tests zu beschleunigen

Da sich die Dateisysteme erheblich unterscheiden, erleichtert die Verwendung von Jimfs auch das einfache Testen mit Dateisystemen verschiedener Betriebssysteme.

3. Maven-Abhängigkeiten

Fügen wir zunächst die Projektabhängigkeiten hinzu, die wir für unsere Beispiele benötigen:

 com.google.jimfs jimfs 1.1 

Die jimfs-Abhängigkeit enthält alles, was wir zur Verwendung unseres verspotteten Dateisystems benötigen. Zusätzlich schreiben wir Tests mit JUnit5.

4. Ein einfaches Datei-Repository

Zunächst definieren wir eine einfache FileRepository- Klasse, die einige Standard-CRUD-Operationen implementiert:

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

Wie wir sehen können, verwendet jede Methode Standardklassen von java.nio .

4.1. Eine Datei erstellen

In diesem Abschnitt schreiben wir einen Test, der die Erstellungsmethode aus unserem Repository testet :

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

In diesem Beispiel haben wir die statische Methode Jimfs.newFileSystem () verwendet , um ein neues In-Memory-Dateisystem zu erstellen. Wir übergeben ein Konfigurationsobjekt Configuration.unix () , das eine unveränderliche Konfiguration für ein Unix-Dateisystem erstellt . Dies umfasst wichtige betriebssystemspezifische Informationen wie Pfadtrennzeichen und Informationen zu symbolischen Links.

Nachdem wir eine Datei erstellt haben, können wir überprüfen, ob die Datei auf dem Unix-basierten System erfolgreich erstellt wurde.

4.2. Eine Datei lesen

Als nächstes testen wir die Methode, mit der der Inhalt der Datei gelesen wird:

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

Dieses Mal haben wir überprüft, ob es möglich ist, den Inhalt der Datei auf einem macOS-System (ehemals OSX) zu lesen , indem wir einfach eine andere Art von Konfiguration verwenden - Jimfs.newFileSystem (Configuration.osX ()) .

4.3. Aktualisieren einer Datei

Wir können Jimfs auch verwenden, um die Methode zu testen, mit der der Inhalt der Datei aktualisiert wird:

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

Ebenso haben wir dieses Mal mithilfe von Jimfs.newFileSystem (Configuration.windows ()) überprüft, wie sich die Methode auf einem Windows-basierten System verhält .

4.4. Datei löschen

Um den Test unserer CRUD-Operationen abzuschließen, testen wir die Methode, mit der die Datei gelöscht wird:

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

Im Gegensatz zu früheren Beispielen haben wir Jimfs.newFileSystem () verwendet, ohne eine Dateisystemkonfiguration anzugeben. In diesem Fall erstellt Jimfs ein neues In-Memory-Dateisystem mit einer Standardkonfiguration, die dem aktuellen Betriebssystem entspricht.

5. Verschieben einer Datei

In diesem Abschnitt erfahren Sie, wie Sie eine Methode testen, mit der eine Datei von einem Verzeichnis in ein anderes verschoben wird.

Implementieren wir zunächst die Verschiebungsmethode mit der Standardklasse 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); } }

Wir werden einen parametrisierten Test verwenden, um sicherzustellen, dass diese Methode auf mehreren verschiedenen Dateisystemen funktioniert:

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

Wie wir sehen können, konnten wir mit Jimfs auch testen, ob wir Dateien auf einer Vielzahl verschiedener Dateisysteme aus einem einzigen Komponententest verschieben können.

6. Betriebssystemabhängige Tests

Um einen weiteren Vorteil der Verwendung von Jimfs zu demonstrieren, erstellen wir eine FilePathReader- Klasse. Die Klasse ist für die Rückgabe des realen Systempfads verantwortlich, der natürlich vom Betriebssystem abhängig ist:

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

Fügen wir nun einen Test für diese Klasse hinzu:

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

Wie wir sehen können, unterscheidet sich die Ausgabe für Windows erwartungsgemäß von der von Unix. Außerdem mussten wir diese Tests nicht mit zwei verschiedenen Dateisystemen ausführen - Jimfs verspottete sie automatisch für uns .

Es ist erwähnenswert, dass Jimfs die toFile () -Methode, die eine java.io.File zurückgibt, nicht unterstützt . Dies ist die einzige Methode aus der Path- Klasse, die nicht unterstützt wird. Daher ist es möglicherweise besser, einen InputStream als eine Datei zu verwenden .

7. Fazit

In diesem Artikel haben wir gelernt, wie Sie das speicherinterne Dateisystem Jimfs verwenden, um Dateisysteminteraktionen aus unseren Komponententests zu verspotten.

Zunächst haben wir ein einfaches Datei-Repository mit mehreren CRUD-Operationen definiert. Dann haben wir Beispiele gesehen, wie jede der Methoden mit einem anderen Dateisystemtyp getestet werden kann. Schließlich haben wir ein Beispiel gesehen, wie wir Jimfs verwenden können, um die betriebssystemabhängige Handhabung von Dateisystemen zu testen.

Wie immer ist der Code für diese Beispiele auf Github verfügbar.