Zombando do sistema de arquivos com o Jimfs

Zombando do sistema de arquivos com o Jimfs

1. Visão geral

Normalmente, ao testar componentes que fazem uso pesado de operações de E / S, nossos testes podem sofrer de vários problemas, como baixo desempenho, dependência da plataforma e estado inesperado.

Neste tutorial,we’ll take a look at how we can alleviate these problems using the in-memory file system Jimfs.

2. Introdução ao Jimfs

e suporta quase todos os recursos. Isso é particularmente útil, pois significa que podemos emular um sistema de arquivos virtual na memória e interagir com ele usando nossa camadajava.nio existente.

Como veremos, pode ser benéfico usar um sistema de arquivos simulado em vez de um real para:

  • evite ser dependente do sistema de arquivos que está executando o teste no momento

  • garantir que o sistema de arquivos seja montado com o estado esperado em cada execução de teste

  • ajudar a acelerar nossos testes

Como os sistemas de arquivos variam consideravelmente, usar o Jimfs também facilita o teste com sistemas de arquivos de diferentes sistemas operacionais.

3. Dependências do Maven

Primeiro de tudo, vamos adicionar as dependências do projeto de que precisaremos para nossos exemplos:


    com.google.jimfs
    jimfs
    1.1

A dependênciajimfs contém tudo o que precisamos para usar nosso sistema de arquivos simulado. Além disso, escreveremos testes usandoJUnit5.

4. Um repositório de arquivos simples

Começaremos definindo uma classeFileRepository simples que implementa algumas operações CRUD padrão:

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

Como podemos ver, cada método está utilizando classes padrãojava.nio.

4.1. Criando um arquivo

Nesta seção, vamos escrever um teste que testa o métodocreate do nosso repositório:

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

Neste exemplo, usamos o métodostaticJimfs.newFileSystem() para criar um novo sistema de arquivos na memória. We pass a configuration object Configuration.unix(), which creates an immutable configuration for a Unix file system. Isso inclui informações importantes específicas do SO, como separadores de caminho e informações sobre links simbólicos.

Agora que criamos um arquivo, podemos verificar se o arquivo foi criado com sucesso no sistema baseado em Unix.

4.2. Lendo um arquivo

A seguir, testaremos o método que lê o conteúdo do arquivo:

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

Desta vez, verificamos se é possível ler o conteúdo do arquivo em ummacOS (formerly OSX) system by simply using a different type of configuration — Jimfs.newFileSystem(Configuration.osX()).

4.3. Atualizando um arquivo

Também podemos usar o Jimfs para testar o método que atualiza o conteúdo do arquivo:

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

Da mesma forma, desta vez, verificamos como o método se comporta em umWindows-based system by using*Jimfs.newFileSystem(Configuration.windows())*.

4.4. Excluindo um arquivo

Para concluir o teste de nossas operações CRUD, vamos testar o método que exclui o arquivo:

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

Ao contrário dos exemplos anteriores, usamosJimfs.newFileSystem() sem especificar uma configuração do sistema de arquivos. Nesse caso, o Jimfs criará um novo sistema de arquivos na memória com uma configuração padrão apropriada ao sistema operacional atual.

5. Movendo um arquivo

Nesta seção, aprenderemos como testar um método que move um arquivo de um diretório para outro.

Em primeiro lugar, vamos implementar o métodomove usando a classejava.nio.file.File padrão:

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

Vamos usar um teste parametrizado para garantir que esse método funcione em vários sistemas de arquivos diferentes:

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

Como podemos ver, também pudemos usar Jimfs para testar se podemos mover arquivos em uma variedade de sistemas de arquivos diferentes a partir de um único teste de unidade.

6. Testes dependentes do sistema operacional

Para demonstrar outro benefício de usar Jimfs, vamos criar uma classeFilePathReader. A classe é responsável por retornar o caminho real do sistema, que é, obviamente, dependente do SO:

class FilePathReader {

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

Agora, vamos adicionar um teste para esta classe:

class FilePathReaderUnitTest {

    private static String DIRECTORY_NAME = "example";

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

Como podemos ver, a saída do Windows difere da do Unix, como era de se esperar. Além disso,we didn’t have to run these tests using two different file systems — Jimfs mocked it for us automatically.

Vale a pena mencionar queJimfs doesn’t support the toFile() method that returns a java.io.File. É o único método da classePath que não é compatível. Portanto, pode ser melhor operar emInputStream em vez deFile.

7. Conclusão

Neste artigo, aprendemos como usar o sistema de arquivos na memória Jimfs para simular as interações do sistema de arquivos em nossos testes de unidade.

Primeiro, começamos definindo um repositório de arquivos simples com várias operações CRUD. Em seguida, vimos exemplos de como testar cada um dos métodos usando um tipo de sistema de arquivos diferente. Por fim, vimos um exemplo de como podemos usar o Jimfs para testar a manipulação do sistema de arquivos dependente do SO.

Como sempre, o código para esses exemplos está disponívelover on Github.