File system beffardo con Jimfs

1. Panoramica

In genere, durante il test di componenti che fanno un uso intenso delle operazioni di I / O, i nostri test possono soffrire di diversi problemi come prestazioni scadenti, dipendenza dalla piattaforma e stato imprevisto.

In questo tutorial, daremo un'occhiata a come possiamo alleviare questi problemi utilizzando il file system in memoria Jimfs.

2. Introduzione a Jimfs

Jimfs è un file system in memoria che implementa l'API NIO Java e ne supporta quasi tutte le funzionalità. Ciò è particolarmente utile, poiché significa che possiamo emulare un filesystem virtuale in memoria e interagire con esso utilizzando il nostro livello java.nio esistente .

Come vedremo, potrebbe essere utile utilizzare un file system deriso invece di uno reale per:

  • Evita di dipendere dal file system che sta attualmente eseguendo il test
  • Assicurati che il filesystem venga assemblato con lo stato previsto in ogni esecuzione di test
  • Aiutaci ad accelerare i nostri test

Poiché i file system variano notevolmente, l'utilizzo di Jimfs facilita anche il test con file system di diversi sistemi operativi.

3. Dipendenze di Maven

Prima di tutto, aggiungiamo le dipendenze del progetto di cui avremo bisogno per i nostri esempi:

 com.google.jimfs jimfs 1.1 

La dipendenza jimfs contiene tutto ciò di cui abbiamo bisogno per utilizzare il nostro file system deriso. Inoltre, scriveremo test utilizzando JUnit5.

4. Un semplice archivio di file

Inizieremo definendo una semplice classe FileRepository che implementa alcune operazioni CRUD standard:

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

Come possiamo vedere, ogni metodo utilizza classi java.nio standard .

4.1. Creazione di un file

In questa sezione, scriveremo un test che verifica il metodo create dal nostro repository:

@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 questo esempio, abbiamo utilizzato il metodo statico Jimfs.newFileSystem () per creare un nuovo file system in memoria. Passiamo un oggetto di configurazione Configuration.unix () , che crea una configurazione immutabile per un file system Unix . Ciò include importanti informazioni specifiche del sistema operativo come separatori di percorso e informazioni sui collegamenti simbolici.

Ora che abbiamo creato un file, siamo in grado di controllare se il file è stato creato con successo sul sistema basato su Unix.

4.2. Leggere un file

Successivamente, testeremo il metodo che legge il contenuto del file:

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

Questa volta, abbiamo verificato se è possibile leggere il contenuto del file su un sistema macOS (ex OSX) semplicemente utilizzando un diverso tipo di configurazione: Jimfs.newFileSystem (Configuration.osX ()) .

4.3. Aggiornamento di un file

Possiamo anche usare Jimfs per testare il metodo che aggiorna il contenuto del file:

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

Allo stesso modo, questa volta abbiamo verificato come si comporta il metodo su un sistema basato su Windows utilizzando Jimfs.newFileSystem (Configuration.windows ()) .

4.4. Eliminazione di un file

Per concludere il test delle nostre operazioni CRUD, testiamo il metodo che elimina il file:

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

A differenza degli esempi precedenti, abbiamo utilizzato Jimfs.newFileSystem () senza specificare una configurazione del file system. In questo caso, Jimfs creerà un nuovo file system in memoria con una configurazione predefinita appropriata al sistema operativo corrente.

5. Spostamento di un file

In questa sezione impareremo come testare un metodo che sposta un file da una directory all'altra.

In primo luogo, implementiamo il metodo move utilizzando la classe java.nio.file.File standard :

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

Utilizzeremo un test parametrizzato per garantire che questo metodo funzioni su diversi file system diversi:

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

Come possiamo vedere, siamo stati anche in grado di utilizzare Jimfs per testare che possiamo spostare file su una varietà di diversi file system da un singolo test unitario.

6. Test dipendenti dal sistema operativo

Per dimostrare un altro vantaggio dell'utilizzo di Jimfs, creiamo una classe FilePathReader . La classe è responsabile della restituzione del percorso di sistema reale, che, ovviamente, dipende dal sistema operativo:

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

Ora aggiungiamo un test per questa classe:

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

Come possiamo vedere, l'output per Windows è diverso da quello di Unix, come ci aspetteremmo. Inoltre, non dovevamo eseguire questi test utilizzando due diversi file system: Jimfs lo ha deriso automaticamente per noi .

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.

Come sempre, il codice per questi esempi è disponibile su Github.