Share
Embed
JUGBB2020: Testcontainers - Past, Present, Future
Sergei Egorov
February 25, 2020
Transcript
137 slides
-
About me • Testcontainers co-maintainer • Staff Engineer at Pivotal’s
Spring R&D, working on Project Reactor ⚛ • Java Champion • Berlin Spring User Group co-organizer • Developer tools geek @bsideup -
Some signs of having Integrated Tests are: • We spin
up other services in a local testing environment • We test against other services in a shared testing environment • Changes to your system breaks tests for other systems https://labs.spotify.com/2018/01/11/testing-of-microservices/ @bsideup -
Mocking Local DBs VMs (Vagrant) Docker Fig (aka Docker Compose)
Integration testing transformation @bsideup -
Docker Compose FTW! redis: image: redis ports: - "6379:6379" postgres:
image: postgres ports: - "5432:5432" elasticsearch: image: elasticsearch:5.0.0 ports: - "9200:9200" @bsideup -
Declarative YAML redis: image: redis ports: - "6379:6379" postgres: image:
postgres ports: - "5432:5432" elasticsearch: image: elasticsearch:5.0.0 ports: - "9200:9200" @bsideup -
No ports randomization redis: image: redis ports: - "6379:6379" postgres:
image: postgres ports: - "5432:5432" elasticsearch: image: elasticsearch:5.0.0 ports: - "9200:9200" @bsideup -
Container per test? redis: image: redis ports: - "6379:6379" postgres:
image: postgres ports: - "5432:5432" elasticsearch: image: elasticsearch:5.0.0 ports: - "9200:9200" @bsideup -
Mocking Local DBs VMs (Vagrant) Docker Fig (aka Docker Compose)
Integration testing transformation @bsideup -
Mocking Local DBs VMs (Vagrant) Docker Fig (aka Docker Compose)
Docker API Integration testing transformation @bsideup -
Testcontainers • Created by Richard North in 2015 • github.com/testcontainers/testcontainers-java
• Wraps docker-java library • Docker environment discovery (Win, Mac, Linux) • Containers cleanup on JVM shutdown @bsideup -
As simple as PostgreSQLContainer postgresql = new PostgreSQLContainer() GenericContainer redis
= new GenericContainer("redis:3") .withExposedPorts(6379) @bsideup -
1.6.x • Kafka module Jan, 2018 @bsideup try (KafkaContainer kafka
= new KafkaContainer()) { kafka.start(); testKafkaFunctionality(kafka.getBootstrapServers()); } -
1.7.x • Maven BOM Apr, 2018 @bsideup <dependencyManagement> <dependencies> <dependency>
<groupId>org.testcontainers</groupId> <artifactId>testcontainers-bom</artifactId> <version>1.11.2</version> <type>bom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> -
1.7.x • Maven BOM • DockerCompose wait strategies Apr, 2018
@bsideup new DockerComposeContainer(new File("docker-compose.yml")) .withExposedService( "redis_1", REDIS_PORT, Wait.forListeningPort() ) .withExposedService( "db_1", 3306, Wait.forLogMessage(".*ready for connections.*\\s", 1) ); -
1.7.x • Maven BOM • DockerCompose wait strategies • Daemon
threads Apr, 2018 @bsideup kiraThread.setDaemon(true); kiraThread.start(); -
1.7.x • Maven BOM • DockerCompose wait strategies • Daemon
threads • MockServer module Apr, 2018 @bsideup try (MockServerContainer mockServer = new MockServerContainer()) { mockServer.start(); String expectedBody = "Hello Default World!"; MockServerClient client = new MockServerClient( mockServer.getContainerIpAddress(), mockServer.getServerPort() ); client.when(request("/hello")).respond(response(expectedBody)); // ... } -
1.8.x • OkHttp transport • Test framework agnostic Jun, 2018
@bsideup public interface Startable extends AutoCloseable { void start(); void stop(); } public interface TestLifecycleAware { default void beforeTest(TestDescription description) {} default void afterTest( TestDescription description, Optional<Throwable> throwable ) {} } -
1.8.x • OkHttp transport • Test framework agnostic • Docker
cred. helpers Jun, 2018 @bsideup { "auths": { }, "HttpHeaders": { "User-Agent": "Docker-Client/18.03.0-ce (darwin)" }, "credHelpers": { "registry.example.com": “helper" } } -
1.8.x • OkHttp transport • Test framework agnostic • Docker
cred. helpers • copyFileToContainer Jun, 2018 @bsideup GenericContainer container = new GenericContainer("alpine:latest") // Look, Ma! No volumes mounting! .withCopyFileToContainer( MountableFile.forClasspathResource(“/mappable-resource/"), containerPath ) -
1.8.x • OkHttp transport • Test framework agnostic • Docker
cred. helpers • copyFileToContainer • Pulsar module • Couchbase module • Cassandra module Jun, 2018 @bsideup -
1.9.x • OkHttp by default • Windows npipe support Sep,
2018 @bsideup No longer needed -
1.9.x • OkHttp by default • Windows npipe support •
Registry auth on Windows Sep, 2018 @bsideup -
1.9.x • OkHttp by default • Windows npipe support •
Registry auth on Windows • Fix local Docker Compose on Windows Sep, 2018 @bsideup new DockerComposeContainer(new File("docker-compose.yml")) .withExposedService("redis_1", REDIS_PORT) .withExposedService("db_1", 3306) .withLocalCompose(true); -
1.9.x • OkHttp by default • Windows npipe support •
Registry auth on Windows • Fix local Docker Compose on Windows • Host ports exposing Sep, 2018 @bsideup @BeforeClass public static void setUp() { localPort = server.getAddress().getPort(); Testcontainers.exposeHostPorts(localPort); } @Rule public BrowserWebDriverContainer browser = new BrowserWebDriverContainer() .withCapabilities(new ChromeOptions()); @Test public void testContainerRunningAgainstExposedHostPort() { RemoteWebDriver webDriver = browser.getWebDriver(); webDriver.get( String.format("http://host.testcontainers.internal:%d/", localPort)); final String pageSource = webDriver.getPageSource(); assertTrue(pageSource.contains("Hello from the host!")); } -
1.9.x • OkHttp by default • Windows npipe support •
Registry auth on Windows • Fix local Docker Compose on Windows • Host ports exposing • Random ports in Couchbase Sep, 2018 @bsideup -
1.9.x • OkHttp by default • Windows npipe support •
Registry auth on Windows • Fix local Docker Compose on Windows • Host ports exposing • Random ports in Couchbase Sep, 2018 @bsideup -
1.9.x • OkHttp by default • Windows npipe support •
Registry auth on Windows • Fix local Docker Compose on Windows • Host ports exposing • Random ports in Couchbase • ClickHouse and Postgis modules Sep, 2018 @bsideup -
1.10.x • JUnit 5 support Nov, 2018 @bsideup @Testcontainers class
MyTestcontainersTests { // will be shared between test methods @Container static final MySQLContainer MYSQL_CONTAINER = new MySQLContainer(); // will be started before and stopped after each test method @Container PostgreSQLContainer postgresqlContainer = new PostgreSQLContainer() .withDatabaseName("foo") .withUsername("foo") .withPassword(“secret"); @Test void test() { assertTrue(MYSQL_CONTAINER.isRunning()); assertTrue(postgresqlContainer.isRunning()); } } -
1.10.x • JUnit 5 support • New docs & more
examples Nov, 2018 @bsideup -
1.10.x • JUnit 5 support • New docs & more
examples • Env var to turn off Ryuk (for public CIs) Nov, 2018 @bsideup -
1.10.x • JUnit 5 support • New docs & more
examples • Env var to turn off Ryuk (for public CIs) • shm + TmpFS settings Nov, 2018 @bsideup -
1.10.x • JUnit 5 support • New docs & more
examples • Env var to turn off Ryuk (for public CIs) • shm + TmpFS settings • Auto dependency updates with Dependabot Nov, 2018 @bsideup -
1.10.x • JUnit 5 support • New docs & more
examples • Env var to turn off Ryuk (for public CIs) • shm + TmpFS settings • Auto dependency updates with Dependabot Nov, 2018 @bsideup -
1.10.x • JUnit 5 support • New docs & more
examples • Env var to turn off Ryuk (for public CIs) • shm + TmpFS settings • Auto dependency updates with Dependabot • Neo4j and Elasticsearch modules Nov, 2018 @bsideup -
1.11.x • Chaos Testing (toxiproxy) Mar, 2019 @bsideup @Rule public
GenericContainer redis = new GenericContainer("redis:5.0.4") .withExposedPorts(6379) .withNetwork(network); @Rule public ToxiproxyContainer toxiproxy = new ToxiproxyContainer() .withNetwork(network); @Test public void testLatencyViaProxy() throws IOException { ContainerProxy proxy = toxiproxy.getProxy(redis, 6379); Jedis jedis = new Jedis( proxy.getContainerIpAddress(), proxy.getProxyPort() ); proxy.toxics() .latency("latency", ToxicDirection.DOWNSTREAM, 1_100) .setJitter(100); jedis.get("somekey"); } -
1.11.x • Chaos Testing (toxiproxy) • fsync=off for PostgreSQL •
Drop Netty transport Mar, 2019 @bsideup -
1.11.x • Chaos Testing (toxiproxy) • fsync=off for PostgreSQL •
Drop Netty transport Mar, 2019 @bsideup -
1.11.x • Chaos Testing (toxiproxy) • fsync=off for PostgreSQL •
Drop Netty transport • Rework shading Mar, 2019 @bsideup -
1.12.x • dependsOn Jul, 2019 @bsideup @Rule KafkaContainer kafka =
new KafkaContainer(); @Rule SchemaContainer schemaRegistryContainer = new SchemaContainer() .withKafka(kafka) .dependsOn(kafka); -
1.12.x • dependsOn • Improved pull handling Jul, 2019 @bsideup
11:15:50.598 INFO [ibmcom/db2express-c:latest] - Pulling docker image: ibmcom/db2express-c:latest. Please 19:34:25.198 INFO [ibmcom/db2express-c:latest] - Pulling image 19:34:25.198 INFO [ibmcom/db2express-c:latest] - Pulling image layers: 0 pending, 0 downloaded, 0 extr 19:34:25.967 INFO [ibmcom/db2express-c:latest] - Pulling image layers: 12 pending, 1 downloaded, 0 extr 19:34:27.363 INFO [ibmcom/db2express-c:latest] - Pulling image layers: 11 pending, 2 downloaded, 0 extr 19:34:58.519 ERROR [ibmcom/db2express-c:latest] - Docker image pull has not made progress in 30s - abortin 19:34:58.564 ERROR [ibmcom/db2express-c:latest] - Failed to pull image: ibmcom/db2express-c:latest. Please Before: After: -
1.12.x • dependsOn • Improved pull handling • Azure Pipelines
for Windows testing Jul, 2019 @bsideup -
1.12.x • dependsOn • Improved pull handling • Azure Pipelines
for Windows testing Jul, 2019 @bsideup -
1.12.x • dependsOn • Improved pull handling • Azure Pipelines
for Windows testing • DB2, CockroachDB and RabbitMQ modules Jul, 2019 @bsideup -
KafkaContainer kafka = new KafkaContainer() .withLogConsumer(new Slf4jLogConsumer(log)) .withEmbeddedZookeeper() .withEnv("FOO", "BAR")
.withStartupAttempts(5); Current DSL @bsideup -
KafkaContainer kafka = new KafkaContainer() .withLogConsumer(new Slf4jLogConsumer(log)) .withEmbeddedZookeeper() .withEnv("FOO", "BAR")
.withStartupAttempts(5); Current DSL GenericContainer @bsideup -
KafkaContainer kafka = new KafkaContainer() .withLogConsumer(new Slf4jLogConsumer(log)) .withEmbeddedZookeeper() .withEnv("FOO", "BAR")
.withStartupAttempts(5); Current DSL KafkaContainer @bsideup -
KafkaContainer kafka = new KafkaContainer() .withLogConsumer(new Slf4jLogConsumer(log)) .withEmbeddedZookeeper() .withEnv("FOO", "BAR")
.withStartupAttempts(5); Current DSL GenericContainer @bsideup -
KafkaContainer kafka = new KafkaContainer() .withLogConsumer(new Slf4jLogConsumer(log)) .withEmbeddedZookeeper() .withEnv("FOO", "BAR")
.withStartupAttempts(5); Current DSL GenericContainer @bsideup -
KafkaContainer kafka = new KafkaContainer() .withLogConsumer(new Slf4jLogConsumer(log)) .withEmbeddedZookeeper() .withEnv("FOO", "BAR")
.withStartupAttempts(5); Current DSL GenericContainer public SELF withStartupAttempts(int attempts) { this.startupAttempts = attempts; return self(); } @bsideup -
public class KafkaContainer extends GenericContainer<KafkaContainer> {} public class GenericContainer<SELF extends
GenericContainer<SELF>> /* */ {} @bsideup -
public class KafkaContainer extends GenericContainer<KafkaContainer> {} public class GenericContainer<SELF extends
GenericContainer<SELF>> /* */ {} https://youtrack.jetbrains.com/issue/KT-17186 https://stackoverflow.com/questions/39163749/how-to-use-a-java-self-bounded-class-in-scala @bsideup -
❌ Hard to maintain ❌ Does not work with Kotlin/Scala
❌ Externally mutable objects @bsideup -
❌ Hard to maintain ❌ Does not work with Kotlin/Scala
❌ Externally mutable objects ❌ “setX” isn’t supported, only “withX” (think collections) @bsideup -
❌ Hard to maintain ❌ Does not work with Kotlin/Scala
❌ Externally mutable objects ❌ “setX” isn’t supported, only “withX” (think collections) ❌ No imperative “if-else” with the fluent style @bsideup -
KafkaContainer kafka = new KafkaContainer() { @Override protected void initialize()
{ withLogConsumer(new Slf4jLogConsumer(log)); withEmbeddedZookeeper(); withEnv("FOO", "BAR"); withStartupAttempts(5); } }; New “DSL”? @bsideup -
KafkaContainer kafka = new KafkaContainer() { @Override protected void initialize()
{ withLogConsumer(new Slf4jLogConsumer(log)); withEmbeddedZookeeper(); withEnv("FOO", "BAR"); withStartupAttempts(5); } }; New “DSL”? Or even… @bsideup -
KafkaContainer kafka = new KafkaContainer() {{ withLogConsumer(new Slf4jLogConsumer(log)); withEmbeddedZookeeper(); withEnv("FOO",
"BAR"); withStartupAttempts(5); }} New “DSL”? @bsideup -
KafkaContainer kafka = new KafkaContainer() {{ withLogConsumer(new Slf4jLogConsumer(log)); withEmbeddedZookeeper(); withEnv("FOO",
"BAR"); withStartupAttempts(5); }} New “DSL”? public void withStartupAttempts(int attempts) { this.startupAttempts = attempts; } @bsideup -
KafkaContainer kafka = new KafkaContainer() {{ withLogConsumer(new Slf4jLogConsumer(log)); withEmbeddedZookeeper(); withEnv("FOO",
"BAR"); withStartupAttempts(5); }} New “DSL”? public void withStartupAttempts(int attempts) { this.startupAttempts = attempts; } @bsideup -
✓ Super easy to maintain ✓ Works with any JVM
language @bsideup -
✓ Super easy to maintain ✓ Works with any JVM
language ✓ “Controllable mutability” - no modifications outside of “initialize” @bsideup -
✓ Super easy to maintain ✓ Works with any JVM
language ✓ “Controllable mutability” - no modifications outside of “initialize” ✓ Void-retuning “setX” can easily be used @bsideup -
✓ Super easy to maintain ✓ Works with any JVM
language ✓ “Controllable mutability” - no modifications outside of “initialize” ✓ Void-retuning “setX” can easily be used ✓ Can use “if-else” @bsideup -
@bsideup // JavaScript var GenericContainer = Java.type(’org.testcontainers.containers.GenericContainer’); var container =
new GenericContainer("nginx"); container.setExposedPorts([80]); container.start(); console.log(container.getContainerIpAddress() + ‘:’ + container.getMappedPort(80)); https://medium.com/graalvm/using-testcontainers-from-a-node-js- application-3aa2273bf3bb -
@bsideup // JavaScript var GenericContainer = Java.type(’org.testcontainers.containers.GenericContainer’); var container =
new GenericContainer("nginx"); container.setExposedPorts([80]); container.start(); console.log(container.getContainerIpAddress() + ‘:’ + container.getMappedPort(80)); https://medium.com/graalvm/using-testcontainers-from-a-node-js- application-3aa2273bf3bb // Python import java generic = java.type('org.testcontainers.containers.GenericContainer') container = generic('nginx') container.setExposedPorts([80]) container.start(); print('%s:%s' % (container.getContainerIpAddress(), container.getMappedPort(80))); -
Testcontainers 1.x architecture Core (Docker, GenericContainer, JUnit 4 int,
test lifecycle) @bsideup -
Testcontainers 1.x architecture Core (Docker, GenericContainer, JUnit 4 int,
test lifecycle) JUnit Jupiter int. @bsideup -
Testcontainers 1.x architecture Core (Docker, GenericContainer, JUnit 4 int,
test lifecycle) JUnit Jupiter int. Modules like MySQL/Kafka/… @bsideup -
Testcontainers 2.x architecture Container-core (Container) Test Frameworks JUnit 4 JUnit
Jupiter Scala test? … @bsideup -
Testcontainers 2.x architecture Container-core (Container) Test Frameworks JUnit 4 JUnit
Jupiter Scala test? … Executing engines Container-core-docker (env discovery, networks, …) Container-core-k8s? … @bsideup -
Modules like MySQL/Kafka/… Testcontainers 2.x architecture Container-core (Container) Test Frameworks
JUnit 4 JUnit Jupiter Scala test? … Executing engines Container-core-docker (env discovery, networks, …) Container-core-k8s? … @bsideup -
✓ Ultra-fast ITDD (Integration Test Driven Development) ✓ Minimal effort
for the users @bsideup -
✓ Ultra-fast ITDD (Integration Test Driven Development) ✓ Minimal effort
for the users ✓ Works for most of the containers @bsideup -
✓ Ultra-fast ITDD (Integration Test Driven Development) ✓ Minimal effort
for the users ✓ Works for most of the containers ✓ Eventually cleanups stale containers (unlike Docker Compose) @bsideup -
✓ Ultra-fast ITDD (Integration Test Driven Development) ✓ Minimal effort
for the users ✓ Works for most of the containers ✓ Eventually cleanups stale containers (unlike Docker Compose) ✓ The alpha version is available NOW (v1.12.3+) @bsideup -
Takeaways • https://testcontainers.org • Works on Linux, Mac and Windows
• …including CIs like Jenkins, Travis, CircleCI, GH Actions, Azure Pipelines, … • Provides a great balance between flexibility, usability, speed and features @bsideup