以下の内容はhttps://kazuhira-r.hatenablog.com/entry/2025/05/31/230607より取得しました。


テストデータをランダムに生成してインスタンスに設定するライブラリー、Instancioを試す

これは、なにをしたくて書いたもの?

Javaのテストデータをランダムに生成するライブラリーのひとつに、Instancioというものがあるのを知ったので少し試してみます。

Instancio

InstancioのWebサイトはこちら。

Instancio: Test Data Generator for Java - Instancio

GitHubリポジトリーはこちら。

GitHub - instancio/instancio: A library that creates fully populated objects for your unit tests.

Instancioはこういうものらしいです。

  • オブジェクトをインスタンス化し、ランダムデータを埋め込むライブラリー
    • テストでの利用を想定しており、テスト実行ごとに新しい入力セットが生成される
  • 単体テストでのデータの手動セットアップに費やす時間とコード量を削減することや、静的なテストデータでは気づかない可能性のあるバグを検出することを狙いとしている
  • リフレクションを使ってネストされたオブジェクトやコレクションを含むオブジェクトにデータを設定できる
    • メソッドを1度呼び出すだけで、データが完全に設定されたインスタンスが生成され、テストケースのインプットとしてすぐに利用できる

ただし、ランダムなテストデータを生成することが目的というよりは、インスタンスに対してランダムなデータを簡単に設定
できるようにすることを目的にしているようです。なので、生成されるデータそのものについては本当にランダムな値になるので、
人名などを設定して欲しい場合などは他のライブラリーを併用することになります。

たとえばDatafakerなどを使うとよいでしょう。

Java Fakerの後継、Datafakerでフェイクデータを作成する - CLOVER🍀

JUnit 5向けのExtensionがあり、それ以外の場合はinstancio-coreを単体で使うようです。

Getting Started - Instancio

RecordsやSealed Classesにも対応しているようです。

ドキュメントはこちら。

User Guide - Instancio

基本的にはこのあたりを見ていけばよさそうです。

User Guide / Instancio Basics

JUnit 5向けのExtensionについてはこちらに書かれていますが、まずは基本的な使い方を押さえる方がよさそうです。

User Guide / JUnit Jupiter Integration

設定については、プログラムまたはプロパティファイルで行うようです。

User Guide / Configuration

ひとまず使ってみます。

環境

今回の環境はこちら。

$ java --version
openjdk 21.0.7 2025-04-15
OpenJDK Runtime Environment (build 21.0.7+6-Ubuntu-0ubuntu124.04)
OpenJDK 64-Bit Server VM (build 21.0.7+6-Ubuntu-0ubuntu124.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.9.9 (8e8579a9e76f7d015ee5ec7bfcdc97d260186937)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 21.0.7, vendor: Ubuntu, runtime: /usr/lib/jvm/java-21-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "6.8.0-60-generic", arch: "amd64", family: "unix"

準備

Maven依存関係など。

    <properties>
        <maven.compiler.release>21</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.13.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.27.3</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.instancio</groupId>
            <artifactId>instancio-core</artifactId>
            <version>5.4.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

Instancioはこちらですね。

        <dependency>
            <groupId>org.instancio</groupId>
            <artifactId>instancio-core</artifactId>
            <version>5.4.1</version>
            <scope>test</scope>
        </dependency>

確認はテストコードで行います。

動作確認用意したクラス。

src/main/java/org/littlewings/instancio/Book.java

package org.littlewings.instancio;

import java.time.LocalDate;

public class Book {
    private String isbn;
    private String title;
    private LocalDate publishDate;
    private int price;
    private Person author;

    // getter/setterは省略

    // toStringも実装、省略
}

src/main/java/org/littlewings/instancio/Person.java

package org.littlewings.instancio;

public class Person {
    private int id;
    private String firstName;
    private String lastName;
    private int age;

    // getter/setterは省略

    // toStringも実装、省略
}

同じような構造をRecordsでも用意しておきます。

src/main/java/org/littlewings/instancio/BookRecord.java

package org.littlewings.instancio;

import java.time.LocalDate;

public record BookRecord(
        String isbn,
        String title,
        LocalDate publishDate,
        int price,
        PersonRecord author
) {
}

src/main/java/org/littlewings/instancio/PersonRecord.java

package org.littlewings.instancio;

public record PersonRecord(
        int id,
        String firstName,
        String lastName,
        int age
) {
}

テストコードの雛形はこんな感じで用意。

src/test/java/org/littlewings/instancio/InstancioTest.java

package org.littlewings.instancio;

import java.time.LocalDate;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.instancio.Instancio;
import org.instancio.Select;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class InstancioTest {

    // ここに、テストを書く!
}

Instancioを試す。

では、Instancioを使ってみましょう。

今回は基本的なところを。

User Guide / Instancio Basics

基本的な使い方

まずはオブジェクトの作成。

User Guide / Instancio Basics / Creating Objects

こんな感じになりました。

    @Test
    void simple() {
        Person person = Instancio.create(Person.class);
        assertThat(person.getId()).isGreaterThan(0);
        assertThat(person.getFirstName()).isNotNull();
        assertThat(person.getLastName()).isNotNull();
        assertThat(person.getAge()).isGreaterThan(0);
        System.out.println(person);

        Book book = Instancio.create(Book.class);
        assertThat(book.getIsbn()).isNotNull();
        assertThat(book.getTitle()).isNotNull();
        assertThat(book.getPublishDate()).isNotNull();
        assertThat(book.getPrice()).isGreaterThan(0);
        System.out.println(book);

        // Records

        PersonRecord personRecord = Instancio.create(PersonRecord.class);
        assertThat(personRecord.id()).isGreaterThan(0);
        assertThat(personRecord.firstName()).isNotNull();
        assertThat(personRecord.lastName()).isNotNull();
        assertThat(personRecord.age()).isGreaterThan(0);
        System.out.println(personRecord);

        BookRecord bookRecord = Instancio.create(BookRecord.class);
        assertThat(bookRecord.isbn()).isNotNull();
        assertThat(bookRecord.title()).isNotNull();
        assertThat(bookRecord.publishDate()).isNotNull();
        assertThat(bookRecord.price()).isGreaterThan(0);
        System.out.println(bookRecord);
    }

Java BeansであってもRecordsであっても簡単にインスタンスにデータを設定できます。

        Person person = Instancio.create(Person.class);

        // Records

        PersonRecord personRecord = Instancio.create(PersonRecord.class);

この時の各オブジェクトに設定された値はこんな感じです。

Person{id=5916, firstName='HXLQJNAT', lastName='KDTZ', age=457}
Book{isbn='RYPEGZWLT', title='BFHP', publishDate=1996-11-09, price=3045, author=Person{id=215, firstName='KSQSGSVGDK', lastName='WQRUMK', age=2880}}
PersonRecord[id=8765, firstName=QBQXDGJIWM, lastName=TNPHCWHF, age=7059]
BookRecord[isbn=TTTJC, title=TIXAQJUMV, publishDate=2059-05-08, price=4612, author=PersonRecord[id=7117, firstName=OHPCLAR, lastName=LEUD, age=9133]]

値はランダムなので、実行ごとに変わります。

コレクションを作成してみます。

User Guide / Instancio Basics / Creating Objects / Creating collections

    @Test
    void collection() {
        List<Person> persons = Instancio.createList(Person.class);
        assertThat(persons).hasSizeGreaterThan(1);
        System.out.println(persons);

        List<Book> books = Instancio.createList(Book.class);
        assertThat(books).hasSizeGreaterThan(1);
        System.out.println(books);

        // Records

        List<PersonRecord> personRecords = Instancio.createList(PersonRecord.class);
        assertThat(personRecords).hasSizeGreaterThan(1);
        System.out.println(personRecords);

        List<BookRecord> bookRecords = Instancio.createList(BookRecord.class);
        assertThat(bookRecords).hasSizeGreaterThan(1);
        System.out.println(bookRecords);
    }

こちらも簡単にListを作成できます。中身がRecordsであってもOKです。

        List<Person> persons = Instancio.createList(Person.class);

        // Records

        List<PersonRecord> personRecords = Instancio.createList(PersonRecord.class);

この時の各Listの中身の出力結果。

[Person{id=8141, firstName='GCICK', lastName='NJFASTJEL', age=4569}, Person{id=7638, firstName='GSRFAY', lastName='QUIAO', age=8500}, Person{id=2494, firstName='JPK', lastName='MZVWZ', age=2746}, Person{id=5695, firstName='JRZFAHLMMB', lastName='AQY', age=6174}]
[Book{isbn='ERQFGCBOLL', title='WXCBEIZE', publishDate=1996-01-11, price=6549, author=Person{id=7922, firstName='TCWD', lastName='DVQAJ', age=1916}}, Book{isbn='XHYQIOKEHL', title='GGVYLSG', publishDate=2028-11-22, price=8693, author=Person{id=8273, firstName='CKPIY', lastName='RWCS', age=1442}}, Book{isbn='KACNZWP', title='VFNKEIFY', publishDate=2018-12-11, price=323, author=Person{id=7098, firstName='ROYY', lastName='RPFMASKWEG', age=3815}}, Book{isbn='GNYIP', title='TOJI', publishDate=2016-01-07, price=1574, author=Person{id=8587, firstName='IAVXTVLQ', lastName='SPTPGPCKNS', age=9048}}, Book{isbn='LFQLMEZ', title='OHEKTRET', publishDate=1981-04-01, price=248, author=Person{id=964, firstName='UFDRBB', lastName='IVVV', age=4404}}]
[PersonRecord[id=1004, firstName=QCJFEITCV, lastName=PIAF, age=7632], PersonRecord[id=8277, firstName=CVFA, lastName=OZX, age=7696], PersonRecord[id=479, firstName=RWW, lastName=LMOTNL, age=1014]]
[BookRecord[isbn=WQCYEF, title=KXXIJUUBZG, publishDate=1991-04-19, price=6548, author=PersonRecord[id=958, firstName=QCVGWHR, lastName=ABMASEALUR, age=5903]], BookRecord[isbn=PJHUQYR, title=PSCCHM, publishDate=1983-06-19, price=5767, author=PersonRecord[id=4793, firstName=IIJOHVB, lastName=FBVZG, age=6286]]]

ランダムに生成されたデータをインスタンスを要素にしたListが作成されていることがわかります。

生成するデータをカスタマイズする

とりあえずインスタンスにランダムなデータが設定できることはわかりましたが、ちょっと意味のない値すぎますね。

Instancioでは生成するオブジェクトをカスタマイズできます。またこの時にSelectorというものも使います。

Java BeansやRecordsに設定するデータをカスタマイズする例。

    @Test
    void customizeSimple() {
        LocalDate now = LocalDate.now();

        Person person = Instancio.of(Person.class)
                .set(Select.field("firstName"), "カツオ")
                .generate(Select.field("lastName"), gen -> gen.oneOf("磯野", "波野"))
                .generate(Select.field("age"), gen -> gen.ints().range(5, 30))
                .create();
        assertThat(person.getFirstName()).isEqualTo("カツオ");
        assertThat(person.getLastName()).isIn("磯野", "波野");
        assertThat(person.getAge()).isGreaterThanOrEqualTo(5).isLessThanOrEqualTo(30);
        System.out.println(person);

        Book book = Instancio.of(Book.class)
                .generate(Select.field("isbn"), gen -> gen.text().pattern("#d#d#d-#d#d#d#d#d#d#d#d#d#d"))
                .supply(Select.field("publishDate"), () -> now)
                .set(Select.field(Person.class, "firstName"), "ワカメ")
                .create();
        assertThat(book.getIsbn()).containsPattern("^\\d{3}-\\d{10}$");
        assertThat(book.getPublishDate()).isEqualTo(now);
        assertThat(book.getAuthor().getFirstName()).isEqualTo("ワカメ");
        System.out.println(book);

        // Records

        PersonRecord personRecord = Instancio.of(PersonRecord.class)
                .set(Select.field("firstName"), "カツオ")
                .generate(Select.field("lastName"), gen -> gen.oneOf("磯野", "波野"))
                .generate(Select.field("age"), gen -> gen.ints().range(5, 30))
                .create();
        assertThat(personRecord.firstName()).isEqualTo("カツオ");
        assertThat(personRecord.lastName()).isIn("磯野", "波野");
        assertThat(personRecord.age()).isGreaterThanOrEqualTo(5).isLessThanOrEqualTo(30);
        System.out.println(personRecord);

        BookRecord bookRecord = Instancio.of(BookRecord.class)
                .generate(Select.field("isbn"), gen -> gen.text().pattern("#d#d#d-#d#d#d#d#d#d#d#d#d#d"))
                .supply(Select.field("publishDate"), () -> now)
                .set(Select.field(PersonRecord.class, "firstName"), "ワカメ")
                .create();
        assertThat(bookRecord.isbn()).containsPattern("^\\d{3}-\\d{10}$");
        assertThat(bookRecord.publishDate()).isEqualTo(now);
        assertThat(bookRecord.author().firstName()).isEqualTo("ワカメ");
        System.out.println(bookRecord);
    }

generateでランダムな値を、supplysetで指定の値に設定できます。
設定しなかったプロパティはランダム値になりますし、Selectorの指定によっては一括でプロパティを指定できたりします。

        Person person = Instancio.of(Person.class)
                .set(Select.field("firstName"), "カツオ")
                .generate(Select.field("lastName"), gen -> gen.oneOf("磯野", "波野"))
                .generate(Select.field("age"), gen -> gen.ints().range(5, 30))
                .create();

        // Records

        PersonRecord personRecord = Instancio.of(PersonRecord.class)
                .set(Select.field("firstName"), "カツオ")
                .generate(Select.field("lastName"), gen -> gen.oneOf("磯野", "波野"))
                .generate(Select.field("age"), gen -> gen.ints().range(5, 30))
                .create();

この時の各オブジェクトに設定された値。

Person{id=2641, firstName='カツオ', lastName='波野', age=23}
Book{isbn='118-5282001588', title='UDOEZSWFOT', publishDate=2025-05-31, price=2473, author=Person{id=2127, firstName='ワカメ', lastName='UEHVF', age=2739}}
PersonRecord[id=9239, firstName=カツオ, lastName=磯野, age=19]
BookRecord[isbn=026-3973232857, title=LMWQFUWUH, publishDate=2025-05-31, price=153, author=PersonRecord[id=3277, firstName=ワカメ, lastName=PJOSYSGYYE, age=1078]]

コレクションでも試してみましょう。

    @Test
    void customizeCollection() {
        Set<Integer> generatedIds = new HashSet<>();

        List<Person> persons = Instancio.ofList(Person.class)
                .size(3)
                .filter(Select.field(Person.class, "id"), generatedIds::add)  // unique id
                .generate(Select.field(Person.class, "lastName"), gen -> gen.oneOf("磯野", "波野"))
                .generate(Select.field(Person.class, "age"), gen -> gen.ints().range(5, 30))
                .create();
        assertThat(persons).hasSize(3);

        persons.forEach(person -> {
            assertThat(person.getId()).isGreaterThan(0);
            assertThat(person.getLastName()).isIn("磯野", "波野");
            assertThat(person.getAge()).isGreaterThanOrEqualTo(5).isLessThanOrEqualTo(30);
        });

        System.out.println(persons);

        List<Book> books = Instancio.ofList(Book.class)
                .size(3)
                .generate(Select.field(Book.class, "isbn"), gen -> gen.text().pattern("#d#d#d-#d#d#d#d#d#d#d#d#d#d"))
                .create();
        assertThat(books).hasSize(3);

        books.forEach(book -> {
            assertThat(book.getIsbn()).containsPattern("^\\d{3}-\\d{10}$");
        });

        System.out.println(books);

        // Records

        List<PersonRecord> personRecords = Instancio.ofList(PersonRecord.class)
                .size(3)
                .filter(Select.field(PersonRecord.class, "id"), generatedIds::add)  // unique id
                .generate(Select.field(PersonRecord.class, "lastName"), gen -> gen.oneOf("磯野", "波野"))
                .generate(Select.field(PersonRecord.class, "age"), gen -> gen.ints().range(5, 30))
                .create();
        assertThat(personRecords).hasSize(3);

        personRecords.forEach(person -> {
            assertThat(person.id()).isGreaterThan(0);
            assertThat(person.lastName()).isIn("磯野", "波野");
            assertThat(person.age()).isGreaterThanOrEqualTo(5).isLessThanOrEqualTo(30);
        });

        System.out.println(personRecords);

        List<BookRecord> bookRecords = Instancio.ofList(BookRecord.class)
                .size(3)
                .generate(Select.field(BookRecord.class, "isbn"), gen -> gen.text().pattern("#d#d#d-#d#d#d#d#d#d#d#d#d#d"))
                .create();
        assertThat(bookRecords).hasSize(3);

        bookRecords.forEach(book -> {
            assertThat(book.isbn()).containsPattern("^\\d{3}-\\d{10}$");
        });

        System.out.println(bookRecords);
    }

PersonのidはList内でユニークになるようにしています。またList内の要素の値を設定する時は、Selectorで型を指定する
必要がありそうです。

        Set<Integer> generatedIds = new HashSet<>();

        List<Person> persons = Instancio.ofList(Person.class)
                .size(3)
                .filter(Select.field(Person.class, "id"), generatedIds::add)  // unique id
                .generate(Select.field(Person.class, "lastName"), gen -> gen.oneOf("磯野", "波野"))
                .generate(Select.field(Person.class, "age"), gen -> gen.ints().range(5, 30))
                .create();


        // Records

        List<PersonRecord> personRecords = Instancio.ofList(PersonRecord.class)
                .size(3)
                .filter(Select.field(PersonRecord.class, "id"), generatedIds::add)  // unique id
                .generate(Select.field(PersonRecord.class, "lastName"), gen -> gen.oneOf("磯野", "波野"))
                .generate(Select.field(PersonRecord.class, "age"), gen -> gen.ints().range(5, 30))
                .create();

この時のListの内容。

[Person{id=8258, firstName='FYJE', lastName='波野', age=19}, Person{id=253, firstName='SFHX', lastName='波野', age=9}, Person{id=1522, firstName='LPCOZFNHCU', lastName='磯野', age=28}]
[Book{isbn='861-3950856326', title='VWYYXB', publishDate=2085-01-02, price=5664, author=Person{id=6795, firstName='CGKDYHRUYK', lastName='JPS', age=2592}}, Book{isbn='192-4679520968', title='KFXLLYBP', publishDate=2024-01-25, price=3307, author=Person{id=6547, firstName='WSC', lastName='AWDXG', age=5260}}, Book{isbn='922-6531220813', title='HQQZB', publishDate=2044-02-19, price=8957, author=Person{id=5250, firstName='JIPCSOV', lastName='HYVFTNOJW', age=3147}}]
[PersonRecord[id=9318, firstName=XTGMW, lastName=波野, age=17], PersonRecord[id=1015, firstName=LCFHFXO, lastName=磯野, age=5], PersonRecord[id=6839, firstName=DJRYIH, lastName=波野, age=29]]
[BookRecord[isbn=971-7188213944, title=LYN, publishDate=2054-02-20, price=8913, author=PersonRecord[id=5828, firstName=FYJJ, lastName=MRJYDSQ, age=2350]], BookRecord[isbn=218-6444546903, title=NTGTJOGYEK, publishDate=2002-09-04, price=5152, author=PersonRecord[id=9477, firstName=CKOUWY, lastName=YZKG, age=5546]], BookRecord[isbn=730-5498630944, title=TPS, publishDate=1996-01-21, price=9855, author=PersonRecord[id=5959, firstName=PBYIYMN, lastName=HNYLATZ, age=5245]]]

他にもいろいろできそうですが、まずはこんなところでしょうか。

おわりに

テストデータをランダムに生成してインスタンスに設定するライブラリーである、Instancioを試してみました。

使い方はそれほど難しくはないのですが、どういうデータを設定すればいいのかは悩みどころな気がしますね。
うまく整理できるといいなとは思うのですが…。

ひとまず、知っておくと便利そうなので押さえておきましょう。




以上の内容はhttps://kazuhira-r.hatenablog.com/entry/2025/05/31/230607より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14