Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Jokerconf 2019: Testcontainers: a year-in-review

Jokerconf 2019: Testcontainers: a year-in-review

Sergei Egorov

October 26, 2019
Tweet

More Decks by Sergei Egorov

Other Decks in Programming

Transcript

  1. About me • Testcontainers co-maintainer • Staff Engineer at Pivotal’s

    Spring R&D, working on Project Reactor ⚛ • Berlin Spring User Group co-organizer • Developer tools geek @bsideup
  2. Integration testing Real-world, but isolated testing Spot the issues before

    the real environment Can be run during the development You have to start real databases Should be cross-platform Slower than Unit testing Pros Cons @bsideup
  3. Mocking Local DBs VMs
 (Vagrant) Docker Fig
 (aka Docker Compose)

    Integration testing transformation @bsideup
  4. Mocking Local DBs VMs
 (Vagrant) Docker Fig
 (aka Docker Compose)

    Docker API Integration testing transformation @bsideup
  5. 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
  6. Declarative YAML redis: image: redis ports: - "6379:6379" postgres: image:

    postgres ports: - "5432:5432" elasticsearch: image: elasticsearch:5.0.0 ports: - "9200:9200" @bsideup
  7. ports randomization? redis: image: redis ports: - "6379:6379" postgres: image:

    postgres ports: - "5432:5432" elasticsearch: image: elasticsearch:5.0.0 ports: - "9200:9200" @bsideup
  8. 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
  9. As simple as PostgreSQLContainer postgresql = new PostgreSQLContainer() GenericContainer redis

    = new GenericContainer("redis:3") .withExposedPorts(6379) @bsideup
  10. 1.6.x • Kafka module Jan, 2018 @bsideup try (KafkaContainer kafka

    = new KafkaContainer()) { kafka.start(); testKafkaFunctionality(kafka.getBootstrapServers()); }
  11. 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>
  12. 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) );
  13. 1.7.x • Maven BOM • DockerCompose
 wait strategies • Daemon

    threads Apr, 2018 @bsideup kiraThread.setDaemon(true); kiraThread.start();
  14. 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)); // ... }
  15. 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 ) {} }
  16. 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" } }
  17. 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 )
  18. 1.8.x • OkHttp transport • Test framework
 agnostic • Docker

    cred. helpers • copyFileToContainer • Pulsar module • Couchbase module • Cassandra module Jun, 2018 @bsideup
  19. 1.9.x • OkHttp by default • Windows npipe support •

    Registry auth on Windows Sep, 2018 @bsideup
  20. 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);
  21. 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!")); }
  22. 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
  23. 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
  24. 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
  25. 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()); } }
  26. 1.10.x • JUnit 5 support • New docs & more

    examples • Env var to turn off Ryuk
 (for public CIs) Nov, 2018 @bsideup
  27. 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
  28. 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
  29. 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
  30. 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
  31. 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"); }
  32. 1.11.x • Chaos Testing (toxiproxy) • fsync=off for PostgreSQL •

    Drop Netty transport • Rework shading Mar, 2019 @bsideup
  33. 1.12.x • dependsOn Jul, 2019 @bsideup @Rule KafkaContainer kafka =

    new KafkaContainer(); @Rule SchemaContainer schemaRegistryContainer = new SchemaContainer() .withKafka(kafka) .dependsOn(kafka);
  34. 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:
  35. 1.12.x • dependsOn • Improved pull handling • Azure Pipelines

    for Windows testing • DB2 and RabbitMQ modules Jul, 2019 @bsideup
  36. 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
  37. 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
  38. ❌ Hard to maintain ❌ Does not work with Kotlin/Scala

    ❌ Externally mutable objects @bsideup
  39. ❌ Hard to maintain ❌ Does not work with Kotlin/Scala

    ❌ Externally mutable objects ❌ “setX” isn’t supported, only “withX” (think collections) @bsideup
  40. ❌ 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
  41. KafkaContainer kafka = new KafkaContainer() { @Override protected void initialize()

    { withLogConsumer(new Slf4jLogConsumer(log)); withEmbeddedZookeeper(); withEnv("FOO", "BAR"); withStartupAttempts(5); } }; New “DSL”? @bsideup
  42. KafkaContainer kafka = new KafkaContainer() { @Override protected void initialize()

    { withLogConsumer(new Slf4jLogConsumer(log)); withEmbeddedZookeeper(); withEnv("FOO", "BAR"); withStartupAttempts(5); } }; New “DSL”? Or even… @bsideup
  43. 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
  44. 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
  45. ✓ Super easy to maintain ✓ Works with any JVM

    language ✓ “Controllable mutability” - no modifications outside of “initialize” @bsideup
  46. ✓ Super easy to maintain ✓ Works with any JVM

    language ✓ “Controllable mutability” - no modifications outside of “initialize” ✓ Void-retuning “setX” can easily be used @bsideup
  47. ✓ 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
  48. @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
  49. @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)));
  50. Testcontainers 1.x architecture Core
 (Docker, GenericContainer, 
 JUnit 4 int,

    test lifecycle) JUnit Jupiter int. Modules like MySQL/Kafka/… @bsideup
  51. Testcontainers 2.x architecture Container-core
 (GenericContainer) Test Frameworks JUnit 4 JUnit

    Jupiter Scala test? … Executing engines Container-core-docker
 (env discovery, networks, …) Container-core-k8s? … @bsideup
  52. Modules like MySQL/Kafka/… Testcontainers 2.x architecture Container-core
 (GenericContainer) Test Frameworks

    JUnit 4 JUnit Jupiter Scala test? … Executing engines Container-core-docker
 (env discovery, networks, …) Container-core-k8s? … @bsideup
  53. ✓ Ultra-fast ITDD (Integration Test Driven Development) ✓ Minimal effort

    for the users ✓ Works for most of the containers @bsideup
  54. ✓ 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
  55. ✓ 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) ✓ Alpha version is available NOW @bsideup
  56. 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