これは、なにをしたくて書いたもの?
Javaのテストデータをランダムに生成するライブラリーのひとつに、Instancioというものがあるのを知ったので少し試してみます。
Instancio
InstancioのWebサイトはこちら。
Instancio: Test Data Generator for Java - Instancio
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を単体で使うようです。
RecordsやSealed Classesにも対応しているようです。
ドキュメントはこちら。
基本的にはこのあたりを見ていけばよさそうです。
JUnit 5向けのExtensionについてはこちらに書かれていますが、まずは基本的な使い方を押さえる方がよさそうです。
User Guide / JUnit Jupiter Integration
設定については、プログラムまたはプロパティファイルで行うようです。
ひとまず使ってみます。
環境
今回の環境はこちら。
$ 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 / 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でランダムな値を、supplyやsetで指定の値に設定できます。
設定しなかったプロパティはランダム値になりますし、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を試してみました。
使い方はそれほど難しくはないのですが、どういうデータを設定すればいいのかは悩みどころな気がしますね。
うまく整理できるといいなとは思うのですが…。
ひとまず、知っておくと便利そうなので押さえておきましょう。