1. 概要

通常、I / O操作を多用するコンポーネントをテストする場合、パフォーマンスの低下、プラットフォームへの依存、予期しない状態など、いくつかの問題が発生する可能性があります。

このチュートリアルでは、メモリ内ファイルシステム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. シンプルなファイルリポジトリ

まず、いくつかの標準的なCRUD操作を実装する単純なFileRepositoryクラスを定義します。

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

この例では、 staticメソッドJimfs.newFileSystem()を使用して、新しいメモリ内ファイルシステムを作成しました。 構成オブジェクトConfiguration.unix()を渡します。これにより、Unixファイルシステムの不変の構成が作成されます。 これには、パス区切り文字やシンボリックリンクに関する情報などの重要なOS固有の情報が含まれます。

ファイルを作成したので、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);
}

今回は、別の種類の構成(Jimfs.newFileSystem(Configuration.osX())を使用するだけで、 macOS(以前の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));
}

同様に、今回は Jimfs.newFileSystem(Configuration.windows())。を使用して、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クラスを使用してmoveメソッドを実装しましょう。

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クラスを作成してみましょう。 このクラスは、実際のシステムパスを返す責任があります。もちろん、これはOSに依存します。

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

ご覧のとおり、Windowsの出力はUnixの出力とは予想どおり異なります。 さらに、 2つの異なるファイルシステムを使用してこれらのテストを実行する必要はありませんでした—Jimfsが自動的にモックアップしました

Jimfsは、java .io.File を返すtoFile()メソッドをサポートしていないことに注意してください。 これは、サポートされていないPathクラスの唯一のメソッドです。 したがって、ファイルよりもInputStreamで操作する方がよい場合があります。

7. 結論

この記事では、メモリ内のファイルシステムJimfsを使用して、単体テストからファイルシステムの相互作用をモックする方法を学びました。

まず、いくつかのCRUD操作を使用して単純なファイルリポジトリを定義することから始めました。 次に、異なるファイルシステムタイプを使用して各メソッドをテストする方法の例を見ました。 最後に、Jimfsを使用してOSに依存するファイルシステムの処理をテストする方法の例を見ました。

いつものように、これらの例のコードはGithubから入手できます。