ããã¯ããªã«ãããããŠæžãããã®ïŒ
ãã¹ããæžãæã«ããã¹ãããŒã¿ã®æ±ãã«ã¯é ãæ©ãŸããŸãã
ããã§ããããã¹ãããŒã¿ããšã¯ãããŒã¿ã¹ãã¢ãäž»ã«ããŒã¿ããŒã¹ã察象ã«ãããã¹ãã§äœ¿ãããŒã¿ã®ããšã§ãã
ã©ãäœã£ããããã®ããã©ãã¡ã³ããã³ã¹ããŠãã£ããããã®ãã ã£ããæ©ã¿ã¯å°œããªãã®ã§ããã
ãã¹ãããŒã¿ã«é¢ãããã¿ãŒã³ããããããªã®ã§ã¡ã¢ããŠãããŸãã
ãã¹ãããŒã¿ã®æ©ã¿
ã²ãšãŸãã察象ã®èšèªã¯JavaãšããŸãããã
ããŒã¿ããŒã¹ã䜿ã£ããã¹ãã«ã€ããŠèª¿ã¹ããšãããããåºãŠããŸãã
- ããŒã¿ããŒã¹ã¢ã¯ã»ã¹ã¯ã¢ãã¯ã«ãã
- ãã¹ãå ã§ããŒã¿ãã»ããã¢ããããã³ãŒããæžã
- DbUnitãªã©ã®ãã¹ãã£ã³ã°ãã¬ãŒã ã¯ãŒã¯ã䜿ã
ãšãŸããç°¡åã«ãã¹ããã§ããä»çµã¿ã ã£ããæ¹æ³ã¯ããã«èŠã€ãããŸãããããã£ãäžããæ¹éã«åã£ããã®ã
éžã¶ããšããæãã§ããã
ãã ãå®éã«ãã£ãŠã¿ããšæ©ãã®ã¯ãã¹ãããŒã¿ã®æ±ãã ã£ããããŸããå ·äœçã«ã¯ããããæ©ã¿ãããããªãšã
ãããŸã§äž»èгã§ãã
ãã¹ãããŒã¿ãäœãã®ã倧å€
ãŸãããã¹ãããŒã¿ãäœãã®ã倧å€ã§ãã
äžã®äžã®ãµã³ãã«ã³ãŒãã ãšããã£ãšæžãŸããŠããããšãå€ãã§ãããå®éã®ã¢ããªã±ãŒã·ã§ã³ãäœãæã«ã¯
å€ãã®ããŒãã«ã®ããŒã¿ãå¿
èŠã«ãªãããšãå€ãã§ãããã
å€éšããŒã䜿ã£ãŠããããšãå€ããšæããŸãã®ã§ããããªããšæ«ç«¯ã®æ©èœã»ã©é¢é£ããããŒãã«ãå¢ããŠãã
èã¥ãåŒã«äœããªããšãããªãããŒã¿ãå€ããªããŸãã
ãããªããšãŸãã©ã®ãããªããŒã¿ãçšæããå¿ èŠãããã®ããèããã®ã倧å€ã§ãã
ãããŠãã¹ãã±ãŒã¹ããšã«å°ãã ãå 容ãéãããŒã¿ãã§ãããããŸãã
ãã¹ãã³ãŒãã®äžã§ãã¹ãããŒã¿ã®ã»ããã¢ãããããããšãå€ããšæããŸããããã®æã«ãã¹ãã³ãŒã
ãã®ãã®ããããã¹ãããŒã¿ãã»ããã¢ããããã³ãŒãã®æ¹ãé·ããªã£ããããŸãã
ã§ã¯SQLãYAMLãªã©ã§çšæããã°ããããšãããšãããã¯æžãå Žæãéãã ããšããæ°ãããŸããã
ææ®µã®è©±ã§ãããšããã®ããã°ã§ã¯Database Riderã䜿ã£ããããŸããã
Database Riderを試してみる - CLOVER🍀
Spring Test × Database Riderで、データを作成する時にテーブル間の依存関係を記録する - CLOVER🍀
ãã¹ãããŒã¿ãå ±éåãããšå€æŽæã®åœ±é¿ã倧ããããå ±éåããªããšäŒŒããããªãã®ãããããå¢ããŠãã
ãã¹ãããŒã¿ãäœãã®ã倧å€ã ãšããã®æéãæžããããã«å
±éåããããªããã®ã§ãã
ãã¹ãããŒã¿ãã³ãŒãã§äœã£ãŠããå Žåã¯ç¹ã«ããããªãšæããŸãã
ãã ãå ±éåããŠäœã£ãŠãããšå€æŽæã®åœ±é¿ç¯å²ããšãŠã倧ãããªããŸãã
ã§ãåã
ã®ãã¹ãã§åããã®ãäœã£ãŠãããšã仿§ãå€ãã£ããããŠå€æŽããæã«ãã£ã±ãä¿®æ£ããªããš
ãããªã察象ãåã
ã«çºçããŸãã
ãã¹ãããŒã¿ãèªã¿ã«ãã
ãã¹ãããŒã¿ã¯æå€ãšèªã¿ã«ããæ°ãããŸããããšãã³ãŒãã§æžãããŠããŠãã
ãã¹ãã³ãŒãã倿Žããæã«ããã®åæãšãªããã¹ãããŒã¿ãæåŸ
å€ãšãªãããŒã¿ãèªã¿è§£ããèŠåŽããããšã
å€ãå°è±¡ãããã®ã§ãããã©ãã§ããããã
ã¢ããªã±ãŒã·ã§ã³ã«æžãããåŠçãšéã£ãŠãããŸãæå¿ïŒïŒïŒãèŠãã«ããã£ãããé ã®äžã§1床ããŒãã«ã§ã®
æã¡æ¹ã«å€æããŠèããããšããã®ã§æèã®ã³ã¹ãã倧ãããããªæ°ãããã®ã§ããã
è€å
ãããŸã§ããã¹ãããŒã¿ã«å¯Ÿããæ©ã¿ãªã®ã§ããããã®æ©ã¿ããããã絡ã¿åã£ãŠè©±ãé£ãããªã£ãŠããæ°ã
ãããã§ãããã
ãã¹ãããŒã¿ãäœãã®ã倧å€ããããåŸã§èŠè¿ããŠãèªã¿ã«ããïŒèªããªãïŒãå
±éåããŠããã®ã§
倿Žããã®ãæãããªãã³ããŒããŠã¡ãã£ãšã ãéããã®ãäœã£ãŠâŠã¿ãããªããšããããããªæ°ãããŸãã
ãããã¿ãªããã©ããã£ãŠä¹ãè¶ããŠãã£ãŠããã§ããããã
ãã¿ãŒã³
ããã¯ãããšããã¹ãããŒã¿ãäœããã¿ãŒã³ããããããªã®ã§ã¡ãã£ãšã¡ã¢ããŠãããŸãããã
- Object Mother
- Test Data Builder
Object Mother
Object Motherã¯Martin Fowlerã解説ããŠãããã¿ãŒã³ã§ãThoughtworksã®ãããžã§ã¯ãã§èæ¡ããããã®ã§ãã
ãã¹ãã§äœ¿çšããããŒã¿ïŒãªããžã§ã¯ãïŒãäœãããã®ãã¿ãŒã³ã§ãã
ãã¹ãã§çµã¿ç«ãŠã以äžã®ãããªã³ãŒãã
Invoice invoice = new Invoice( new Recipient("Sherlock Holmes", new Address("221b Baker Street", "London", new PostCode("NW1", "3RX"))), new InvoiceLines( new InvoiceLine("Deerstalker Hat", new PoundsShillingsPence(0, 3, 10)), new InvoiceLine("Tweed Cape", new PoundsShillingsPence(0, 4, 12))));
ãã¡ã¯ããªãŒã¡ãœããã«ãŸãšããŸãããã®ãã¡ã¯ããªãŒã¡ãœãããå®çŸ©ãããã¯ã©ã¹ãObject Motherã§ãã
Invoice invoice = TestInvoices.newDeerstalkerAndCapeInvoice();
Object Motherã䜿ãããšã§ããã¹ãããŒã¿ïŒãªããžã§ã¯ãïŒãçæããã³ãŒãããã¹ãã³ãŒãããåé¢ã
ãã¡ã¯ããªãŒã¡ãœããã«é©åãªã¡ãœãããã€ããããšã§å¯èªæ§ãåäžããŸãããã¹ãéã§ã®åå©çšãå¯èœã§ãã
ãªã®ã§ãããObject Motherã¯ãã¹ãããŒã¿ã®ããªãšãŒã·ã§ã³ã«å¯Ÿå¿ã§ããŸãããå°ãç°ãªããã¹ãããŒã¿ã
å¿
èŠã«ãªããšãObject Motherã«å¥ã®ãã¡ã¯ããªãŒã¡ãœããã远å ãããŸãã
Invoice invoice1 = TestInvoices.newDeerstalkerAndCapeAndSwordstickInvoice(); Invoice invoice2 = TestInvoices.newDeerstalkerAndBootsInvoice();
ãã¡ãã®ããŒãžã«ã¯ãObject Motherã¯ã¢ã³ããã¿ãŒã³ãªã®ã§ã¯ïŒãšæžãããŠããŸãã
ãã®ãããªçµæãObject Motherããã¹ãŠã®ãã®ã飲ã¿èŸŒãã God ClassïŒGod Object MotherïŒã«
ãªããããããã§ããçµæãšããŠã¡ã³ããã³ã¹ãå°é£ã«ãªã£ãŠãããŸãã
Object Motherã§ã¯åºå®ã®ããŒã¿ã«ãªã£ãŠããŸããããBuilderãã¿ãŒã³ãé©çšããæ¹ãããã®ã§ã¯ãªãã
ãšããããšã§åºãŠããã®ãTest Data Builderã§ãã
Test Data Builder
Test Data Builderã¯Nat Pryceããå®è·µãã¹ãé§åéçºãã®äžã§ãObject Motherã®åŒ±ç¹ãè¿°ã¹ãããã§
玹ä»ããŠãããã¿ãŒã³ã§ãã
Mistaeks I Hav Made: Test Data Builders: an alternative to the Object Mother pattern
実践テスト駆動開発 テストに導かれてオブジェクト指向ソフトウェアを育てる(和智 右桂 和智 右桂 髙木 正弘 髙木 正弘 Steve Freeman Nat Pryce)|翔泳社の本
ã¡ãªã¿ã«ãObject Motherã®ç޹ä»ã§æžãããµã³ãã«ã³ãŒãã¯Test Data Builderã®ãšã³ããªãŒããæã£ãŠãããã®ã§ãã
å®è·µãã¹ãé§åéçºã§ããã䌌ãã³ãŒããæžãããŠããŸãã
以äžã¯Test Data Builderã®äŸã§ãã
public class InvoiceBuilder { Recipient recipient = new RecipientBuilder().build(); InvoiceLines lines = new InvoiceLines(new InvoiceLineBuilder().build()); PoundsShillingsPence discount = PoundsShillingsPence.ZERO; public InvoiceBuilder withRecipient(Recipient recipient) { this.recipient = recipient; return this; } public InvoiceBuilder withInvoiceLines(InvoiceLines lines) { this.lines = lines; return this; } public InvoiceBuilder withDiscount(PoundsShillingsPence discount) { this.discount = discount; return this; } public Invoice build() { return new Invoice(recipient, lines, discount); } }
ãã€ã³ãã¯ä»¥äžã«ãªããŸãã
- äŸåããã€ã³ã¹ã¿ã³ã¹ããã£ãŒã«ãã«ä¿æããŠãã
- buildã¡ãœããã§äŸåããã€ã³ã¹ã¿ã³ã¹ãå«ããŠã察象ã®ã€ã³ã¹ã¿ã³ã¹ãæ§ç¯ãã
- ã€ã³ã¹ã¿ã³ã¹ã¯æå®ããªããã°ããã©ã«ãå€ã§åæåãã
- Builderã®ã€ã³ã¹ã¿ã³ã¹ãè¿ãã¡ãœãããæã¡ãå éšã§ä¿æããŠããã€ã³ã¹ã¿ã³ã¹ãã«ã¹ã¿ãã€ãºã§ãã
ããã©ã«ãå€ã§ãããã°ã以äžã®åŒã³åºãã§å¯Ÿè±¡ã®ã€ã³ã¹ã¿ã³ã¹ãæã«å ¥ããŸãã
Invoice anInvoice = new InvoiceBuilder().build();
Builderã䜿ãããšã§ãã€ã³ã¹ã¿ã³ã¹ã®å€ãã«ã¹ã¿ãã€ãºã§ããŸãã
Invoice invoiceWithNoPostcode = new InvoiceBuilder() .withRecipient(new RecipientBuilder() .withAddress(new AddressBuilder() .withNoPostcode() .build()) .build()) .build(); new AddressBuilder() .withName("Sherlock Holmes") .withStreet("221b Baker Street") .withCity("London") .withPostCode("NW1", "3RX") .build();
ãŸãBuilderã«å¥ã®Builderãæž¡ããŠã€ã³ã¹ã¿ã³ã¹ãã«ã¹ã¿ãã€ãºã§ããããã«ããŠãããã§ãããã
ã§ãã©ããªã®ïŒ
ã§ãTest Data Builderã䜿ãã°èªåãæã£ãŠããæ©ã¿ãã€ã³ãã¯ãã£ãã解決ããããšãããšãæå€ãšããã§ã
ãããŸããã
ãã®ããããçç±ã§ããã
- Builderãäœãã®ã倧å€
- Builderã§ã«ã¹ã¿ãã€ãºããããã¹ãããŒã¿ãèªã¿è§£ãã®ã倧å€
- Builderã§äœãããGod Object Motherãã§ããããå¯èœæ§ãé«ã
ãŸããããŸãæ¹æ³ã¯ãªããšããæ°ã¯ããŸãã
ãããã
- Object Motherã®èšäºã¯2006幎ã®ãã®
- Test Data Builderã®èšäºã¯2007幎ã®ãã®
- ããŒã¿ããŒã¹ã䜿ã£ããã¹ãã£ã³ã°ãã¬ãŒã ã¯ãŒã¯ããªããªãæµè¡ããªã
ãšããæãã§ããããã
Database RiderãAseertJ-DBã®æŽæ°ãæ»ã£ãŠããã®ãèŠããšããªããªãå³ãããã ãããªãšã
å²ãšJavaå¯ãã®è©±ã«ããŠããŸãããä»ã®èšèªã£ãŠDbUnitã®ãããªç«ã¡äœçœ®ã®ã©ã€ãã©ãªãŒã£ãŠãããŸããªãæ°ã
ããŸãã
Test Data BuilderïŒéåžžã®ã¢ãµãŒã·ã§ã³ããšããã®ãèœã¡çãå ãªã®ãããããŸããã
æåŸã«è»œãInstancioã䜿ã£ãŠTest Data Builderã詊ããŠã¿ãŸããããInstancioã¯ãã¹ãããŒã¿ãã©ã³ãã ã«
çæããã©ã€ãã©ãªãŒã§ãã
テストデータをランダムに生成してインスタンスに設定するライブラリー、Instancioを試す - CLOVER🍀
ç°å¢
ä»åã®ç°å¢ã¯ãã¡ãã
$ java --version openjdk 25.0.2 2026-01-20 OpenJDK Runtime Environment (build 25.0.2+10-Ubuntu-124.04) OpenJDK 64-Bit Server VM (build 25.0.2+10-Ubuntu-124.04, mixed mode, sharing) $ mvn --version Apache Maven 3.9.12 (848fbb4bf2d427b72bdb2471c22fced7ebd9a7a1) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 25.0.2, vendor: Ubuntu, runtime: /usr/lib/jvm/java-25-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "6.8.0-100-generic", arch: "amd64", family: "unix"
ããŒã¿ããŒã¹ã«ã¯MySQLã䜿ããŸãã
MySQL localhost:3306 ssl practice SQL > select version(); +-----------+ | version() | +-----------+ | 8.4.8 | +-----------+ 1 row in set (0.0006 sec)
MySQLã¯172.17.0.2ã§åäœããŠãããã®ãšããŸãã
æºå
MavenäŸåé¢ä¿ãªã©ã¯ãã¡ãã
<properties> <maven.compiler.release>25</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties> <dependencies> <dependency> <groupId>org.seasar.doma</groupId> <artifactId>doma-core</artifactId> <version>3.11.1</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>9.6.0</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>6.0.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.27.7</version> </dependency> <dependency> <groupId>org.instancio</groupId> <artifactId>instancio-core</artifactId> <version>5.5.1</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.14.1</version> <configuration> <annotationProcessorPaths> <path> <groupId>org.seasar.doma</groupId> <artifactId>doma-processor</artifactId> <version>3.11.1</version> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins> </build>
ããŒã¿ããŒã¹ã¢ã¯ã»ã¹ã©ã€ãã©ãªãŒãšããŠã¯Domaã䜿ããŸãã
ãé¡
ä»åã®ãé¡ã¯ãããã°ãšãã®èè ã«ããŸãããã
ããããããŒãã«ã«ããŸãã
create table if not exists author( id varchar(36), first_name varchar(10), last_name varchar(10), age integer, primary key(id) ); create table if not exists author( id varchar(36), first_name varchar(10), last_name varchar(10), age integer, primary key(id) );
ãœãŒã¹ã³ãŒããäœæãã
ãã®ãé¡ã§ãœãŒã¹ã³ãŒããäœæããŠãããŸãã
Domaã®ãšã³ãã£ãã£ãŒã¯ã©ã¹ã
src/main/java/org/littlewings/testdatabuilder/entity/Author.java
package org.littlewings.testdatabuilder.entity; import org.seasar.doma.Entity; import org.seasar.doma.Id; @Entity public record Author( @Id String id, String firstName, String lastName, Integer age ) { }
src/main/java/org/littlewings/testdatabuilder/entity/Post.java
package org.littlewings.testdatabuilder.entity; import java.time.LocalDate; import org.seasar.doma.Entity; import org.seasar.doma.Id; @Entity public record Post( @Id String id, String title, String url, LocalDate date, String authorId ) { }
Daoã
src/main/java/org/littlewings/testdatabuilder/dao/AuthorDao.java
package org.littlewings.testdatabuilder.dao; import java.util.List; import org.littlewings.testdatabuilder.entity.Author; import org.seasar.doma.Dao; import org.seasar.doma.Delete; import org.seasar.doma.Insert; import org.seasar.doma.Script; import org.seasar.doma.Select; import org.seasar.doma.Sql; import org.seasar.doma.jdbc.Result; @Dao public interface AuthorDao { @Sql(""" create table if not exists author( id varchar(36), first_name varchar(10), last_name varchar(10), age integer, primary key(id) ) """) @Script void createTableIfExistsRecreate(); @Sql("delete from author") @Delete int deleteAll(); @Insert Result<Author> insert(Author author); @Sql("select /*%expand*/* from author where id = /* id */'dummy'") @Select Author selectById(String id); @Sql("select /*%expand*/* from author order by age desc") @Select List<Author> selectAllOrderByAgeDesc(); }
src/main/java/org/littlewings/testdatabuilder/dao/PostDao.java
package org.littlewings.testdatabuilder.dao; import java.util.List; import org.littlewings.testdatabuilder.entity.Post; import org.seasar.doma.Dao; import org.seasar.doma.Delete; import org.seasar.doma.In; import org.seasar.doma.Insert; import org.seasar.doma.Script; import org.seasar.doma.Select; import org.seasar.doma.Sql; import org.seasar.doma.jdbc.Result; @Dao public interface PostDao { @Sql(""" create table if not exists post( id varchar(36), title varchar(200), url text, date date, author_id varchar(36), primary key(id), foreign key(author_id) references author(id) ) """) @Script void createTableIfExistsRecreate(); @Sql("delete from post") @Delete int deleteAll(); @Insert Result<Post> insert(Post post); @Sql("select /*%expand*/* from post order by date desc") @Select List<Post> selectAllOrderByDateDesc(); }
Domaã®èšå®ã¯ã©ã¹ã
src/main/java/org/littlewings/testdatabuilder/DomaConfig.java
package org.littlewings.testdatabuilder; import javax.sql.DataSource; import org.seasar.doma.jdbc.Config; import org.seasar.doma.jdbc.Naming; import org.seasar.doma.jdbc.dialect.Dialect; import org.seasar.doma.jdbc.dialect.MysqlDialect; import org.seasar.doma.jdbc.tx.LocalTransactionDataSource; import org.seasar.doma.jdbc.tx.LocalTransactionManager; import org.seasar.doma.jdbc.tx.TransactionManager; public class DomaConfig implements Config { private static final DomaConfig INSTANCE = new DomaConfig(); private Dialect dialect; private LocalTransactionDataSource dataSource; private TransactionManager transactionManager; private DomaConfig() { dialect = new MysqlDialect(MysqlDialect.MySqlVersion.V8); dataSource = new LocalTransactionDataSource( "jdbc:mysql://172.17.0.2:3306/practice?characterEncoding=utf-8&connectionCollation=utf8mb4_0900_bin", "kazuhira", "password" ); transactionManager = new LocalTransactionManager(dataSource.getLocalTransaction(getJdbcLogger())); } public static DomaConfig singleton() { return INSTANCE; } @Override public DataSource getDataSource() { return dataSource; } @Override public Dialect getDialect() { return dialect; } @Override public TransactionManager getTransactionManager() { return transactionManager; } @Override public Naming getNaming() { return Naming.SNAKE_LOWER_CASE; } }
ãã¹ãã³ãŒããæžã
ã§ã¯ããã¹ãã³ãŒããæžããŠãããŸãã
ãã¹ãã³ãŒãã®é圢ã¯ãã¡ãã
src/test/java/org/littlewings/testdatabuilder/TestDataBuilderTest.java
package org.littlewings.testdatabuilder; import java.net.URL; import java.time.LocalDate; import java.util.List; import org.instancio.Instancio; import org.instancio.Select; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.littlewings.testdatabuilder.dao.AuthorDao; import org.littlewings.testdatabuilder.dao.AuthorDaoImpl; import org.littlewings.testdatabuilder.dao.PostDao; import org.littlewings.testdatabuilder.dao.PostDaoImpl; import org.littlewings.testdatabuilder.entity.Author; import org.littlewings.testdatabuilder.entity.Post; import static org.assertj.core.api.Assertions.assertThat; class TestDataBuilderTest { @BeforeAll static void setUpAll() { AuthorDao authorDao = new AuthorDaoImpl(DomaConfig.singleton()); authorDao.createTableIfExistsRecreate(); PostDao postDao = new PostDaoImpl(DomaConfig.singleton()); postDao.createTableIfExistsRecreate(); } @BeforeEach void setUp() { PostDao postDao = new PostDaoImpl(DomaConfig.singleton()); postDao.deleteAll(); AuthorDao authorDao = new AuthorDaoImpl(DomaConfig.singleton()); authorDao.deleteAll(); } // ããã«ãã¹ããæžãïŒ }
Instancioããã®ãŸãŸäœ¿ã
ãŸãã¯ã·ã³ãã«ã«Instancioã䜿ã£ãŠãã¹ããæžããŠã¿ãŸãã
ãããªæãã§ããããã
@Test void authors() { Author katsuo = Instancio.of(Author.class) .set(Select.field("firstName"), "ã«ããª") .set(Select.field("age"), 11) .create(); Author wakame = Instancio.of(Author.class) .set(Select.field("firstName"), "ã¯ã«ã¡") .set(Select.field("age"), 9) .create(); DomaConfig .singleton() .getTransactionManager() .required(() -> { AuthorDao authorDao = new AuthorDaoImpl(DomaConfig.singleton()); authorDao.insert(katsuo); authorDao.insert(wakame); Author foundKatsuo = authorDao.selectById(katsuo.id()); assertThat(foundKatsuo).isEqualTo(katsuo); Author foundWakame = authorDao.selectById(wakame.id()); assertThat(foundWakame).isEqualTo(wakame); }); }
ããæå³ãããTest Data Builderãšèšããæ°ã¯ããŸããã
Instancioã䜿ã£ãŠTest Data Builderãäœæãã
次ã¯Instancioã䜿ã£ãŠTest Data BuilderãäœããŸãã
ãããªæãã§ããããã
src/test/java/org/littlewings/testdatabuilder/AuthorFactory.java
package org.littlewings.testdatabuilder; import java.util.function.Consumer; import org.instancio.Instancio; import org.instancio.InstancioApi; import org.instancio.Select; import org.littlewings.testdatabuilder.dao.AuthorDao; import org.littlewings.testdatabuilder.entity.Author; public class AuthorFactory { private InstancioApi<Author> authorCreator; private AuthorDao authorDao; private AuthorFactory(AuthorDao authorDao) { this.authorDao = authorDao; this.authorCreator = Instancio.of(Author.class) .generate(Select.field("id"), gen -> gen.string().minLength(36).maxLength(36)) .generate(Select.field("firstName"), gen -> gen.string().maxLength(10)) .generate(Select.field("lastName"), gen -> gen.string().maxLength(10)) .generate(Select.field("age"), gen -> gen.ints().min(0).max(100)); } public static AuthorFactory anAuthor(AuthorDao authorDao) { return new AuthorFactory(authorDao); } public AuthorFactory firstName(String firstName) { authorCreator.set(Select.field("firstName"), firstName); return this; } public AuthorFactory age(int age) { authorCreator.set(Select.field("age"), age); return this; } public AuthorFactory with(Consumer<InstancioApi<Author>> consumer) { consumer.accept(authorCreator); return this; } public Author build() { return authorCreator.create(); } public Author create() { Author instance = build(); authorDao.insert(instance); return instance; } }
src/test/java/org/littlewings/testdatabuilder/PostFactory.java
package org.littlewings.testdatabuilder; import java.net.URL; import java.time.LocalDate; import java.util.function.Consumer; import org.instancio.Instancio; import org.instancio.InstancioApi; import org.instancio.Select; import org.littlewings.testdatabuilder.dao.PostDao; import org.littlewings.testdatabuilder.entity.Author; import org.littlewings.testdatabuilder.entity.Post; public class PostFactory { private InstancioApi<Post> postCreator; private PostDao postDao; private PostFactory(PostDao postDao) { this.postDao = postDao; this.postCreator = Instancio.of(Post.class) .generate(Select.field("id"), gen -> gen.string().minLength(36).maxLength(36)) .generate(Select.field("title"), gen -> gen.string().maxLength(200)) .generate(Select.field("url"), gen -> gen.net().url().protocol("https").as(URL::toString)); } public static PostFactory aPost(PostDao postDao) { return new PostFactory(postDao); } public static PostFactory aPost(PostDao postDao, Author author) { return aPost(postDao).withAuthor(author); } public PostFactory date(LocalDate date) { postCreator.set(Select.field("date"), date); return this; } public PostFactory with(Consumer<InstancioApi<Post>> consumer) { consumer.accept(postCreator); return this; } public PostFactory withAuthor(Author author) { postCreator.set(Select.field("authorId"), author.id()); return this; } public Post build() { return postCreator.create(); } public Post create() { Post instance = build(); postDao.insert(instance); return instance; } }
çæããã€ã³ã¹ã¿ã³ã¹ã®å€ã¯å°ã ã«ã¹ã¿ãã€ãºããŠãããŸãã
private AuthorFactory(AuthorDao authorDao) { this.authorDao = authorDao; this.authorCreator = Instancio.of(Author.class) .generate(Select.field("id"), gen -> gen.string().minLength(36).maxLength(36)) .generate(Select.field("firstName"), gen -> gen.string().maxLength(10)) .generate(Select.field("lastName"), gen -> gen.string().maxLength(10)) .generate(Select.field("age"), gen -> gen.ints().min(0).max(100)); } private PostFactory(PostDao postDao) { this.postDao = postDao; this.postCreator = Instancio.of(Post.class) .generate(Select.field("id"), gen -> gen.string().minLength(36).maxLength(36)) .generate(Select.field("title"), gen -> gen.string().maxLength(200)) .generate(Select.field("url"), gen -> gen.net().url().protocol("https").as(URL::toString)); }
ããŒã¿ããŒã¹ã«ä¿åããã¿ã€ãã³ã°ãã¡ãã£ãšæ©ã¿ãŸãããããªããžã§ã¯ãã®æ§ç¯æã«insertããããšã«ããŸããã
public Author build() { return authorCreator.create(); } public Author create() { Author instance = build(); authorDao.insert(instance); return instance; }
insertããã«ã€ã³ã¹ã¿ã³ã¹åãŸã§ã§ãšã©ããŠããã¡ãœãããçšæããŠããŸãã
ãããã圹å²ããã£ãé¢ä¿ã§ãTest Data Builderãšãããã¿ãŒã³ã®å²ã«ã¯ã©ã¹åãBuilderã«ããã«Factoryã«ããŸããã
䜿ããšãããªæãã§ããã
@Test void authorsWithBuilder() { DomaConfig .singleton() .getTransactionManager() .required(() -> { AuthorDao authorDao = new AuthorDaoImpl(DomaConfig.singleton()); Author katsuo = AuthorFactory.anAuthor(authorDao) .firstName("ã«ããª") .age(11) .create(); Author wakame = AuthorFactory.anAuthor(authorDao) .firstName("ã¯ã«ã¡") .age(9) .create(); Author foundKatsuo = authorDao.selectById(katsuo.id()); assertThat(foundKatsuo).isEqualTo(katsuo); Author foundWakame = authorDao.selectById(wakame.id()); assertThat(foundWakame).isEqualTo(wakame); }); } @Test void postsWithBuilder() { DomaConfig .singleton() .getTransactionManager() .required(() -> { AuthorDao authorDao = new AuthorDaoImpl(DomaConfig.singleton()); PostDao postDao = new PostDaoImpl(DomaConfig.singleton()); Author katsuo = AuthorFactory.anAuthor(authorDao) .firstName("ã«ããª") .age(11) .create(); Author wakame = AuthorFactory.anAuthor(authorDao) .firstName("ã¯ã«ã¡") .age(9) .create(); Post katsuoPost1 = PostFactory.aPost(postDao, katsuo) .date(LocalDate.of(2026, 1, 31)) .create(); Post katsuoPost2 = PostFactory.aPost(postDao, katsuo) .date(LocalDate.of(2026, 2, 5)) .create(); Post wakamePost1 = PostFactory.aPost(postDao, wakame) .date(LocalDate.of(2026, 2, 1)) .create(); List<Post> posts = postDao.selectAllOrderByDateDesc(); assertThat(posts).hasSize(3); assertThat(posts).isEqualTo(List.of(katsuoPost2, wakamePost1, katsuoPost1)); }); }
ãããããTest Data Builderãšã¯ã¡ãã£ãšèšãé£ãã§ããããŸãšãããã®ãäœã£ãŠã¿ãŸããã
src/test/java/org/littlewings/testdatabuilder/IsonoFamilyPostFactory.java
package org.littlewings.testdatabuilder; import java.time.LocalDate; import java.util.List; import org.littlewings.testdatabuilder.dao.AuthorDao; import org.littlewings.testdatabuilder.dao.PostDao; import org.littlewings.testdatabuilder.entity.Author; import org.littlewings.testdatabuilder.entity.Post; public class IsonoFamilyPostFactory { private List<AuthorFactory> authorFactories; private List<PostFactory> postFactories; private IsonoFamilyPostFactory(AuthorDao authorDao, PostDao postDao) { this.authorFactories = List.of( AuthorFactory.anAuthor(authorDao) .firstName("ã«ããª") .age(11), AuthorFactory.anAuthor(authorDao) .firstName("ã¯ã«ã¡") .age(9) ); this.postFactories = List.of( PostFactory.aPost(postDao) .date(LocalDate.of(2026, 1, 31)), PostFactory.aPost(postDao) .date(LocalDate.of(2026, 2, 5)), PostFactory.aPost(postDao) .date(LocalDate.of(2026, 2, 1)) ); } public static IsonoFamilyPostFactory family(AuthorDao authorDao, PostDao postDao) { return new IsonoFamilyPostFactory(authorDao, postDao); } public List<Post> create() { List<Author> authors = authorFactories.stream().map(AuthorFactory::create).toList(); postFactories.get(0).withAuthor(authors.get(0)); postFactories.get(1).withAuthor(authors.get(0)); postFactories.get(2).withAuthor(authors.get(1)); return postFactories.stream().map(PostFactory::create).toList(); } }
ãã¹ãåŽã
@Test void isonoFamiry() { DomaConfig .singleton() .getTransactionManager() .required(() -> { AuthorDao authorDao = new AuthorDaoImpl(DomaConfig.singleton()); PostDao postDao = new PostDaoImpl(DomaConfig.singleton()); IsonoFamilyPostFactory isonoFamilyPostFactory = IsonoFamilyPostFactory.family(authorDao, postDao); List<Post> isonoFamilyPosts = isonoFamilyPostFactory.create(); List<Post> posts = postDao.selectAllOrderByDateDesc(); assertThat(posts).hasSize(isonoFamilyPosts.size()); assertThat(posts).isEqualTo(List.of(isonoFamilyPosts.get(1), isonoFamilyPosts.get(2), isonoFamilyPosts.get(0))); }); }
é°å²æ°ãæŽãããã«æžããŠã¿ãŸãããããããªãšããã§ããããã
å®éã«æžããŠã¿ãŠïŒ
ä»åãTest Data BuilderãšInstancioãçµã¿åãããŠã¿ãŸãããããŸãé£ããã§ããã
æåã«æã£ãããã«ããããªãæãããããããŸããã
- Builderãäœãã®ã倧å€
- Builderã§ã«ã¹ã¿ãã€ãºããããã¹ãããŒã¿ãèªã¿è§£ãã®ã倧å€
- Builderã§äœãããGod Object Motherãã§ããããå¯èœæ§ãé«ã
Builderã®èšèšãé£ããã®ãšãå€ã«åããšèªåãã䜿ããªãTest Data Builderãã§ããããããã ãªããšã
ãŸãããŒã¿ããŒã¹ã«ä¿åããã¿ã€ãã³ã°ãåä»ã§ãããã«ããŒã¿éã®äŸåé¢ä¿ãå
¥ã£ãŠãããšTest Data Builderã®èšèšã
ããªãé£ãããªããŸããæåŸã«äœã£ããã¹ãããŒã¿ããŸãšããã¯ã©ã¹ã§ããã«ãã£ã¬ã³ãžãããããªãšæã£ãã®ã§ããã
ä»åã®ãšã³ããªãŒãšããŠã¯è©±ãããããããªããããã®ã§ãããŸããã
ããããããããæ±çšçã«äœãã®ã¯å³ããã§ããããããšããå°è±¡ãæã¡ãŸããã
ãã 課é¡ãšããŠã¯ãããªãšæã£ãã®ã§ãããŒã¿éã®äŸåé¢ä¿ãèžãŸããããã§ãŸããã£ã¬ã³ãžãããã§ããã
ãããã«
Object MotherãšTest Data Builderã«ã€ããŠèª¿ã¹ã€ã€ãTest Data BuilderãšInstancioãçµã¿åãããŠãã¹ãããŒã¿ãäœæããŠ
ã¿ãŸããã
åçŽã«Builderãäœãã ããªãããã®ã§ãããå®éã«ãã¹ããããªããããŒã¿ãäœã仿ããèãããšãªããšäžæ°ã«é£æåºŠã
è·³ãäžããæ°ãããŸãã
æåŸã®æ¹ã«ãæžããŸãããããã課é¡ã ãšã¯æãã®ã§ãŸãèããŠã¿ããã§ãã