Testcontainers ã¯ãéçºããã³ãã¹ãã®ãŠãŒã¹ ã±ãŒã¹çšã«äœ¿ãæšãŠã®ãªã³ããã³ã ã³ã³ãããŒãããããžã§ãã³ã°ããããã®ãªãŒãã³ ãœãŒã¹ ãã¬ãŒã ã¯ãŒã¯ã§ãã ãã¹ãã³ã³ããã䜿çšãããšãããŒã¿ããŒã¹ãã¡ãã»ãŒãžãããŒã«ãŒãWebãã©ãŠã¶ããŸãã¯Dockerã³ã³ããã§å®è¡ã§ããã»ãŒãã¹ãŠã®ãã®ãç°¡åã«æäœã§ããŸãã
ããŒã«ã«éçºã« Testcontainers ã©ã€ãã©ãªã䜿çšããããšãã§ããŸããTestcontainers ã©ã€ãã©ãªãš Testcontainers Desktop ãçµã¿åããããšãå¿«é©ãªããŒã«ã«éçºãšãã¹ãã®ãšã¯ã¹ããªãšã³ã¹ãæäŸãããŸãã Testcontainers ã©ã€ãã©ãªã¯ã Javaã Goã .NETã Node.jsã Pythonã Rubyã Rustã Clojureã Haskellã
ãã®èšäºã§ã¯ãTestcontainers ã©ã€ãã©ãªã䜿çšããéã®ããã€ãã®ãã¹ãããšãšãã¹ãã§ãªãããšã«ã€ããŠèª¬æããŸãã ããã§ã¯Javaã®ã³ãŒãã¹ããããã玹ä»ããŸããããã®æŠå¿µã¯ä»ã®èšèªã«ãé©çšã§ããŸãã

ãã¹ããåºå®ããŒãã«äŸåããªã
Testcontainers ã䜿ãå§ããã°ããã®å Žåããæ¢åã®ãã¹ãèšå®ã Testcontainers ã䜿çšããããã«å€æããå Žåã¯ãã³ã³ãããŒã«åºå®ããŒãã䜿çšããããšãæ€èšããŠãã ããã
ããšãã°ãPostgreSQL ãã¹ãããŒã¿ããŒã¹ãã€ã³ã¹ããŒã«ãããããŒã 5432ã§å®è¡ãããŠããçŸåšã®ãã¹ãã»ããã¢ãããããããã¹ãããã®ããŒã¿ããŒã¹ãšéä¿¡ãããšããŸãã æåã§ã€ã³ã¹ããŒã«ããããŒã¿ããŒã¹ã䜿çšãã代ããã«ãPostgreSQL ããŒã¿ããŒã¹ãå®è¡ããããã« Testcontainers ãå©çšããããšãããšãPostgreSQL ã³ã³ãããŒãèµ·åãããã¹ãäžã®åºå®ããŒã 5432 ã§å ¬éããããšãèãããããããŸããã
ãã ãããã¹ãã®å®è¡äžã«ã³ã³ãããŒã«åºå®ããŒãã䜿çšããããšã¯ã次ã®çç±ãããå§ãã§ããŸããã
- ããªããŸãã¯ããªãã®ããŒã ã¡ã³ããŒã¯ãåãããŒãã§å¥ã®ããã»ã¹ãå®è¡ããŠããå¯èœæ§ãããããã®å Žåããã¹ãã¯å€±æããŸãã
- ç¶ç¶çã€ã³ãã°ã¬ãŒã·ã§ã³ (CI) ç°å¢ã§ãã¹ããå®è¡ããŠããéã¯ãè€æ°ã®ãã€ãã©ã€ã³ã䞊è¡ããŠå®è¡ããããšãã§ããŸãã ãã€ãã©ã€ã³ã¯ãåãåºå®ããŒãã§åãçš®é¡ã®è€æ°ã®ã³ã³ãããŒãèµ·åããããšããå ŽåããããããŒãã®ç«¶åãçºçããŸãã
- ãã¹ã ã¹ã€ãŒããããŒã«ã«ã§äžŠååãããšãåãã³ã³ãããŒã®è€æ°ã®ã€ã³ã¹ã¿ã³ã¹ãåæã«å®è¡ãããŸãã
ãããã®åé¡ãå®å šã«åé¿ããã«ã¯ãTestcontainers ã®çµã¿èŸŒã¿ã®åçããŒã ãããã³ã°æ©èœã䜿çšããã®ãæåã®æ¹æ³ã§ãã
// Example 1:
GenericContainer<?> redis =
new GenericContainer<>("redis:5.0.3-alpine")
.withExposedPorts(6379);
int mappedPort = redis.getMappedPort(6379);
// if there is only one port exposed then you can use redis.getFirstMappedPort()
// Example 2:
PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:16-alpine");
int mappedPort = postgres.getMappedPort(5432);
String jdbcUrl = postgres.getJdbcUrl();
ãã¹ãã«åºå®ããŒãã䜿çšããããšã¯åŒ·ããå§ãããŸããããããŒã«ã«éçºã«åºå®ããŒãã䜿çšãããšäŸ¿å©ã§ãã ããã«ãããããšãã°ããŒã¿ããŒã¹æ€æ»ããŒã«ã䜿çšããå Žåãªã©ãäžè²«ããããŒãã䜿çšããŠãµãŒãã¹ã«æ¥ç¶ã§ããŸãã Testcontainers Desktop ã䜿çšãããšã åºå®ããŒãã§ãããã®ãµãŒãã¹ã«ç°¡åã«æ¥ç¶ã§ããŸãã
ãã¹ãåãããŒãã³ãŒãããªã
ãã¹ãã« Testcontainers ã䜿çšããå Žåã¯ãåžžã«ãã¹ããšããŒãã®å€ãåçã«èšå®ããå¿ èŠããããŸãã ããšãã°ãRedisã³ã³ããã䜿çšããäžè¬çãªSpring Bootãã¹ãã¯æ¬¡ã®ããã«ãªããŸãã
@SpringBootTest(webEnvironment = RANDOM_PORT)
@Testcontainers
class MyControllerTest {
@Container
static GenericContainer<?> redis =
new GenericContainer<>(DockerImageName.parse("redis:5.0.3-alpine"))
.withExposedPorts(6379);
@DynamicPropertySource
static void overrideProperties(DynamicPropertyRegistry registry) {
registry.add("spring.redis.host", () -> "localhost");
registry.add("spring.redis.port", () -> redis.getMappedPort(6379));
}
@Test
void someTest() {
....
}
}
ç±å¿ãªèгå¯è ãšããŠãRedisãã¹ãã localhostãšããŠããŒãã³ãŒãã£ã³ã°ããŠããããšã«æ°ä»ãããããããŸããã ãã¹ããå®è¡ãããšãã³ã³ãããŒã®ããããããããŒãã« localhost ãä»ããŠã¢ã¯ã»ã¹ã§ããããã«æ§æãããããŒã«ã«ã® Docker ããŒã¢ã³ã䜿çšããŠããéããCI ã§æ£åžžã«åäœããå®è¡ãããŸãã
ãã ãããªã¢ãŒã Docker ããŒã¢ã³ã䜿çšããããã«ç°å¢ãæ§æãããšããããã®ã³ã³ãããŒã localhost ã§å®è¡ãããªããªãããããã¹ãã¯å€±æããŸãã ãããã£ãŠããã¹ããå®å šã«ç§»æ€å¯èœã«ããããã®ãã¹ããã©ã¯ãã£ã¹ã¯ãredis.getHost()ã䜿çšããããšã§ã次ã®ããã«ããŒãã³ãŒãããã localhost ã®ä»£ããã«:
@DynamicPropertySource
static void overrideProperties(DynamicPropertyRegistry registry) {
registry.add("spring.redis.host", () -> redis.getHost());
registry.add("spring.redis.port", () -> redis.getMappedPort(6379));
}
ã³ã³ãããŒåãããŒãã³ãŒãã£ã³ã°ããªã
ã³ã³ããã«ååãä»ããã«ã¯ã次ã®ããã«ããŸã withCreateContainerCmdModifier(..) ã
PostgreSQLContainer<?> postgres=
new PostgreSQLContainer<>("postgres:16-alpine")
.withCreateContainerCmdModifier(cmd -> cmd.withName("postgres"));
ãã ããã³ã³ããã«åºå®/ããŒãã³ãŒããããååãä»ãããšãåãååã§è€æ°ã®ã³ã³ãããå®è¡ããããšãããšåé¡ãçºçããŸãã ããã«ãããè€æ°ã®ãã€ãã©ã€ã³ã䞊è¡ããŠå®è¡ããŠãããšãã« CI ç°å¢ã§åé¡ãçºçããå¯èœæ§ããããŸãã
çµéšåãšããŠãç¹å®ã®äžè¬çãªDockeræ©èœ(ã³ã³ããåãªã©)ãTestcontainers APIã§äœ¿çšã§ããªãå Žåãããã¯çµ±åãã¹ãã®ãã¹ããã©ã¯ãã£ã¹ã®äœ¿çšãä¿é²ããç¬æçãªæ±ºå®ã«ãªãåŸåããããŸãã ãã㯠withCreateContainerCmdModifier() ãéåžžã«ç¹æ®ãªãŠãŒã¹ã±ãŒã¹ãæã€ããTestcontainersã®èšèšäžã®æ±ºå®ãåé¿ããããã«äœ¿çšãã¹ãã§ã¯ãªãçµéšè±å¯ãªãŠãŒã¶ãŒåãã®é«åºŠãªæ©èœãšããŠå©çšã§ããŸãã
ãã¡ã€ã«ãããŠã³ãããã®ã§ã¯ãªããã³ã³ããã«ã³ããŒãã
ãã¹ãçšã®ã³ã³ãããŒãæ§æãããšãã«ãã³ã³ãããŒå ã®ç¹å®ã®å Žæã«ããã€ãã®ããŒã«ã« ãã¡ã€ã«ãã³ããŒã§ããŸãã å žåçãªäŸã¯ãããŒã¿ããŒã¹åæåSQLã¹ã¯ãªãããããŒã¿ããŒã¹ã³ã³ããå ã®ã©ããã«ã³ããŒããããšã§ãã
ããã¯ã次ã®ããã«ããŒã«ã«ãã¡ã€ã«ãã³ã³ããã«ããŠã³ãããããšã§æ§æã§ããŸãã
PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:16-alpine")
.withFileSystemBind(
"src/test/resources/schema.sql",
"/docker-entrypoint-initdb.d/01-schema.sql",
BindMode.READ_ONLY);
ããã¯ããŒã«ã«ã§æ©èœããå¯èœæ§ããããŸãã ãã ãããªã¢ãŒãDockerããŒã¢ã³ãŸãã¯Testcontainers Cloudã䜿çšããŠããå Žåããããã®ãã¡ã€ã«ã¯ãªã¢ãŒãDockerãã¹ãã«èŠã€ãããããã¹ãã¯å€±æããŸãã
ããŒã«ã«ãã¡ã€ã«ãããŠã³ããã代ããã«ã次ã®ããã«ãã¡ã€ã«ã³ããŒã䜿çšããå¿ èŠããããŸãã
PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:16-alpine")
.withCopyFileToContainer(
MountableFile.forClasspathResource("schema.sql"),
"/docker-entrypoint-initdb.d/01-schema.sql");
ãã®ã¢ãããŒãã¯ããªã¢ãŒãDockerããŒã¢ã³ãŸãã¯Testcontainers Cloudã䜿çšããŠããå Žåã§ãæ£åžžã«æ©èœãããã¹ããç§»æ€å¯èœã«ããŸãã
éçšç°å¢ãšåãã³ã³ãã㌠ããŒãžã§ã³ã䜿çšãã
ã³ã³ãã㌠ã¿ã°ãæå®ãããšãã¯ãæ°ããããŒãžã§ã³ã®ã€ã¡ãŒãžããªãªãŒã¹ããããšãã«ãã¹ãã«äžå®å®ããçããå¯èœæ§ãããããã latest ã䜿çšããªãã§ãã ããã 代ããã«ãéçšç°å¢ã§äœ¿çšããã®ãšåãããŒãžã§ã³ã䜿çšããŠããã¹ãã®çµæãä¿¡é Œã§ããããšã確èªããŸãã
ããšãã°ãPostgreSQL 15.æ¬çªç°å¢ã§2 ããŒãžã§ã³ã®å Žåã¯ãpostgres:15ã䜿çšããŸãã2 ãã¹ããããŒã«ã«éçºçšã®Dockerã€ã¡ãŒãžãå¿ èŠã§ãã
// DON'T DO THIS
PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:latest");
// INSTEAD, DO THIS
PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:15.2");
é©åãªã³ã³ããã©ã€ããµã€ã¯ã«æŠç¥ã䜿çšãã
éåžžãã¯ã©ã¹å ã®ãã¹ãŠã®ãã¹ãã«ã¯ã次ã®ããã«åãã³ã³ãããŒã䜿çšãããŸãã
@SpringBootTest(webEnvironment = RANDOM_PORT)
@Testcontainers
class MyControllerTest {
@Container
static GenericContainer<?> redis =
new GenericContainer<>("redis:5.0.3-alpine")
.withExposedPorts(6379);
@DynamicPropertySource
static void overrideProperties(DynamicPropertyRegistry registry) {
registry.add("spring.redis.host", () -> "localhost");
registry.add("spring.redis.port", () -> redis.getMappedPort(6379));
}
@Test
void firstTest() {
....
}
@Test
void secondTest() {
....
}
}
å®è¡ãã MyControllerTestãšã1 ã€ã® Redis ã³ã³ãããŒã®ã¿ãèµ·åãããäž¡æ¹ã®ãã¹ãã®å®è¡ã«äœ¿çšãããŸãã ããã¯ãRedisã³ã³ããã éç ãã£ãŒã«ãã«ããŠããããã§ãã éçãã£ãŒã«ãã§ãªãå Žåã¯ã2 ã€ã®ãã¹ãã®å®è¡ã« 2 ã€ã® Redis ã€ã³ã¹ã¿ã³ã¹ã䜿çšãããŸãããããã¯æãŸãããªãå¯èœæ§ããããSpring ã³ã³ããã¹ããåäœæããŠããªãå Žåã¯å€±æããå¯èœæ§ããããŸãã ãã¹ãããšã«åå¥ã®ã³ã³ãããŒã䜿çšããããšã¯å¯èœã§ããããªãœãŒã¹ã倧éã«æ¶è²»ãããã¹ãã®å®è¡ãé
ããªãå¯èœæ§ããããŸãã
ãŸããTestcontainers ã®ã©ã€ããµã€ã¯ã«ã«æ
£ããŠããªãéçºè
ã¯ãJUnit 5 Extension ã¢ãããŒã·ã§ã³@Testcontainersã䜿çšããã@Containercontainer.start()ã ãã㊠container.stop() ã¡ãœããã Testcontainers ã®ã©ã€ããµã€ã¯ã«æ¹æ³ãå®å
šã«çè§£ããã«ã¯ãJUnit 5ã䜿çšãã Testcontainers ã³ã³ããã®ã©ã€ããµã€ã¯ã«ç®¡çã¬ã€ãããèªã¿ãã ããã
ãã¹ãã®å®è¡ãé«éåããããã®å¥ã®äžè¬çãªã¢ãããŒãã¯ã ã·ã³ã°ã«ãã³ ã³ã³ãã㌠ãã¿ãŒã³ã䜿çšããããšã§ãã
ãã¬ãŒã ã¯ãŒã¯ã®çµ±åãTestcontainersã«æŽ»çšãã
Spring BootãQuarkusãMicronautãªã©ã®äžéšã®ãã¬ãŒã ã¯ãŒã¯ã¯ãTestcontainersã«ããã«çµ±åã§ããŸãããããã®ãã¬ãŒã ã¯ãŒã¯ã®ããããã䜿çšããŠã¢ããªã±ãŒã·ã§ã³ãæ§ç¯ããå Žåã¯ããã¬ãŒã ã¯ãŒã¯ã® Testcontainers çµ±åãµããŒãã䜿çšããããšããå§ãããŸãã
- Testcontainers ã® Spring Boot ãµããŒã
- Testcontainers ã䜿çšãã Quarkus DevServices
- Micronaut TestResources(ãã¹ãã³ã³ããã䜿çš)
å¯èœãªå Žåã¯ããŠãŒã¶ãŒãäºåèšå®ãããã¯ãããžãŒåºæã®ã¢ãžã¥ãŒã«
Testcontainersã¯ãSQLããŒã¿ããŒã¹ãNoSQLããŒã¿ã¹ãã¢ãã¡ãã»ãŒãžãããŒã«ãŒãæ€çŽ¢ãšã³ãžã³ãªã©ãäžè¬çãªãã¯ãããžãŒã®ã»ãšãã©ã«å¯Ÿå¿ãã ãã¯ãããžãŒåºæã®ã¢ãžã¥ãŒã« ãæäŸããŸãã ãããã®ã¢ãžã¥ãŒã«ã¯ãSQL ããŒã¿ããŒã¹ ã³ã³ãããŒããã® JDBC URLãKafka ã³ã³ãããŒããã® bootstrapServers URL ã®ååŸãªã©ãã³ã³ãããŒã®æ å ±ãç°¡åã«ååŸã§ãããã¯ãããžåºæã® API ãæäŸããŸãã æãéèŠãªã®ã¯ãå¿ èŠãªãã¹ãŠã®ããŒãã¹ãã©ããäœæ¥ãåŠçããã³ã³ããã§ã¢ããªã±ãŒã·ã§ã³ãç°¡åã«å®è¡ããJavaã³ãŒããã察話ã§ããããã«ããããšã§ãã
ããšãã°ãPostgreSQL ã³ã³ãããŒã®äœæã« ã䜿çšãããš GenericContainer ãæ¬¡ã®ããã«ãªããŸãã
GenericContainer<?> postgres = new GenericContainer<>("postgres:16-alpine")
.withExposedPorts(5432)
.withEnv("POSTGRES_USER", "test")
.withEnv("POSTGRES_PASSWORD", "test")
.withEnv("POSTGRES_DB", "test")
.waitingFor(
new LogMessageWaitStrategy()
.withRegEx(".*database system is ready to accept connections.*\\s")
.withTimes(2).withStartupTimeout(Duration.of(60L, ChronoUnit.SECONDS)));
postgres.start();
String jdbcUrl = String.format(
"jdbc:postgresql://%s:%d/test", postgres.getHost(),
postgres.getFirstMappedPort());
Testcontainers PostgreSQL ã¢ãžã¥ãŒã«ã䜿çšãããšã次ã®ããã«ç°¡åã« PostgreSQL ã³ã³ãããŒã®ã€ã³ã¹ã¿ã³ã¹ãäœæã§ããŸãã
PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine");
String jdbcUrl = postgres.getJdbcUrl();
PostgreSQLã¢ãžã¥ãŒã«ã®å®è£ ã¯ããã§ã«è³¢æãªããã©ã«ããé©çšããŠãããã³ã³ããæ å ±ãååŸããããã®äŸ¿å©ãªã¡ãœãããæäŸããŠããŸãã
ãã®ããã ã䜿çšãã GenericContainer代ããã«ããŸããç®çã®ãã¯ãããžã® ã¢ãžã¥ãŒã«ãã¢ãžã¥ãŒã«ã«ã¿ãã° ã«æ¢ã«ãããã©ããã確èªããŸãã
äžæ¹ãã«ã¿ãã°ããéèŠãªã¢ãžã¥ãŒã«ãæ¬ èœããŠããå Žåã¯ãçŽæ¥äœ¿çšãã GenericContainer (ãŸã㯠GenericContainer ãæ¡åŒµããç¬èªã®ã«ã¹ã¿ã ã¯ã©ã¹ãäœæãã) ããšã§ããã¯ãããžãæ©èœãããããšãã§ããå¯èœæ§ãé«ããªããŸãã
WaitStrategies ã䜿çšããŠã³ã³ãããŒã®æºåãã§ããŠããããšã確èªãã
ç¬èªã®ã¢ãžã¥ãŒã«ãäœ¿çš GenericContainer ãŸãã¯äœæããŠããå Žåã¯ãé©å㪠WaitStrategy ã䜿çšããŠãã¹ãªãŒããæ° (ããªç§) ç§é䜿çšãã代ããã«ãã³ã³ãããŒãå®å
šã«åæåããã䜿çšã§ããç¶æ
ã«ãªã£ãŠãããã©ããã確èªããŸãã
//DON'T DO THIS
GenericContainer<?> container = new GenericContainer<>("image:tag")
.withExposedPorts(9090);
container.start();
Thread.sleep(2 * 1000); //waiting for container to be ready
container.getHost();
container.getFirstMappedPort();
//DO THIS
GenericContainer<?> container = new GenericContainer<>("image:tag")
.withExposedPorts(9090)
.waitingFor(Wait.forLogMessage(".*Ready to accept connections.*\\n", 1));
container.start();
container.getHost();
container.getFirstMappedPort();
Testcontainers ã®èšèªåºæã®ããã¥ã¡ã³ãã§ãããã«äœ¿çšã§ãã WaitStrategies ã確èªããŠãã ããã å¿ èŠã«å¿ããŠãç¬èªã®å®è£ ãè¡ãããšãã§ããŸããÂ
泚: WaitStrategy ãæ§æããªãå ŽåãTestcontainers ã¯ããã¹ãããå ¬éãããŠãããã¹ãŠã®ããŒãã®æ¥ç¶ããã§ãã¯ããæ¢å®ã® WaitStrategy ãèšå®ããŸãã
æŠèŠ
Testcontainers ã©ã€ãã©ãªã䜿çšããéã®ãã¹ãããšãšãã¹ãã§ãªãããšã®ããã€ããæ€èšããããåªããä»£æ¿ææ®µãæäŸããŸããã Testcontainers ã® Web ãµã€ãããã§ãã¯ã ãŠããã¬ãŒã ã¯ãŒã¯ã广çã«äœ¿çšããæ¹æ³ã«é¢ãããã®ä»ã®ãªãœãŒã¹ãèŠã€ããŠãã ããã
詳ããèŠã
- Testcontainers Cloud ã¢ã«ãŠã³ãã«ãµã€ã³ã¢ããããŸãã
- Testcontainers Slack ã§æ¥ç¶ããŸãã
- Testcontainers ã¬ã€ãããéå§ããŸãã
- Docker Newsletter ã賌èªããŠãã ããã
- 質åããããŸãã? Docker ã³ãã¥ããã£ããæäŒãããŸãã
- ããã«ãŒã¯åããŠã§ãã?å§ããŸãããã