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. Testcontainers:

    a year-in-review
    Sergei Egorov
    @bsideup

    View Slide

  2. 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

    View Slide

  3. Integration testing
    Why it matters?

    View Slide

  4. @bsideup

    View Slide

  5. @bsideup

    View Slide

  6. 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

    View Slide

  7. Integration testing transformation
    @bsideup

    View Slide

  8. Mocking
    Integration testing transformation
    @bsideup

    View Slide

  9. Mocking
    Local DBs
    Integration testing transformation
    @bsideup

    View Slide

  10. Mocking
    Local DBs
    VMs

    (Vagrant)
    Integration testing transformation
    @bsideup

    View Slide

  11. Mocking
    Local DBs
    VMs

    (Vagrant)
    Docker
    Integration testing transformation
    @bsideup

    View Slide

  12. Mocking
    Local DBs
    VMs

    (Vagrant)
    Docker
    Fig

    (aka Docker Compose)
    Integration testing transformation
    @bsideup

    View Slide

  13. Mocking
    Local DBs
    VMs

    (Vagrant)
    Docker
    Fig

    (aka Docker Compose)
    Docker API
    Integration testing transformation
    @bsideup

    View Slide

  14. View Slide

  15. Abstraction layer

    View Slide

  16. CI friendly

    View Slide

  17. Cross-platform

    View Slide

  18. 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

    View Slide

  19. But…

    View Slide

  20. Declarative YAML
    redis:
    image: redis
    ports:
    - "6379:6379"
    postgres:
    image: postgres
    ports:
    - "5432:5432"
    elasticsearch:
    image: elasticsearch:5.0.0
    ports:
    - "9200:9200"
    @bsideup

    View Slide

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

    View Slide

  22. 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

    View Slide

  23. IDE integration?
    @bsideup

    View Slide

  24. Fighting with Docker environment

    View Slide

  25. There is no place like

    View Slide

  26. There is no place like
    … unless there is

    View Slide

  27. Can we improve that?

    View Slide

  28. View Slide

  29. Testcontainers
    • http://github.com/testcontainers/testcontainers-java
    • Wraps docker-java library

    • Docker environment discovery (Win, Mac, Linux)

    • Containers cleanup on JVM shutdown
    @bsideup

    View Slide

  30. As simple as
    PostgreSQLContainer postgresql = new PostgreSQLContainer()
    GenericContainer redis = new GenericContainer("redis:3")
    .withExposedPorts(6379)
    @bsideup

    View Slide

  31. Users

    View Slide

  32. @bsideup

    View Slide

  33. Demo

    View Slide

  34. 1.6.x
    Jan, 2018
    @bsideup

    View Slide

  35. 1.6.x
    • Kafka module
    Jan, 2018
    @bsideup
    try (KafkaContainer kafka = new KafkaContainer()) {
    kafka.start();
    testKafkaFunctionality(kafka.getBootstrapServers());
    }

    View Slide

  36. 1.6.x
    • Kafka module

    • “Ryuk”
    Jan, 2018
    @bsideup

    View Slide

  37. 1.7.x
    Apr, 2018
    @bsideup

    View Slide

  38. 1.7.x
    • Maven BOM
    Apr, 2018
    @bsideup



    org.testcontainers
    testcontainers-bom
    1.11.2
    bom
    import



    View Slide

  39. 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)
    );

    View Slide

  40. 1.7.x
    • Maven BOM

    • DockerCompose

    wait strategies

    • Daemon threads
    Apr, 2018
    @bsideup
    kiraThread.setDaemon(true);
    kiraThread.start();

    View Slide

  41. 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));
    // ...
    }

    View Slide

  42. 1.8.x
    Jun, 2018
    @bsideup

    View Slide

  43. 1.8.x
    • OkHttp transport
    Jun, 2018
    @bsideup

    View Slide

  44. 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
    ) {}
    }

    View Slide

  45. 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"
    }
    }

    View Slide

  46. 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
    )

    View Slide

  47. 1.8.x
    • OkHttp transport

    • Test framework

    agnostic

    • Docker cred. helpers

    • copyFileToContainer

    • Pulsar module
    • Couchbase module
    • Cassandra module
    Jun, 2018
    @bsideup

    View Slide

  48. 1.9.x
    Sep, 2018
    @bsideup

    View Slide

  49. 1.9.x
    • OkHttp by default
    Sep, 2018
    @bsideup

    View Slide

  50. 1.9.x
    • OkHttp by default

    • Windows npipe support
    Sep, 2018
    @bsideup
    No longer needed

    View Slide

  51. 1.9.x
    • OkHttp by default

    • Windows npipe support

    • Registry auth on Windows
    Sep, 2018
    @bsideup

    View Slide

  52. 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);

    View Slide

  53. 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!"));
    }

    View Slide

  54. 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

    View Slide

  55. 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

    View Slide

  56. 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

    View Slide

  57. 1.10.x
    Nov, 2018
    @bsideup

    View Slide

  58. 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());
    }
    }

    View Slide

  59. 1.10.x
    • JUnit 5 support

    • New docs & more examples
    Nov, 2018
    @bsideup

    View Slide

  60. 1.10.x
    • JUnit 5 support

    • New docs & more examples

    • Env var to turn off Ryuk

    (for public CIs)
    Nov, 2018
    @bsideup

    View Slide

  61. 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

    View Slide

  62. 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

    View Slide

  63. 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

    View Slide

  64. 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

    View Slide

  65. 1.11.x
    Mar, 2019
    @bsideup

    View Slide

  66. 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");
    }

    View Slide

  67. 1.11.x
    • Chaos Testing (toxiproxy)

    • fsync=off for PostgreSQL
    Mar, 2019
    @bsideup

    View Slide

  68. 1.11.x
    • Chaos Testing (toxiproxy)

    • fsync=off for PostgreSQL

    • Drop Netty transport
    Mar, 2019
    @bsideup

    View Slide

  69. 1.11.x
    • Chaos Testing (toxiproxy)

    • fsync=off for PostgreSQL

    • Drop Netty transport
    Mar, 2019
    @bsideup

    View Slide

  70. 1.11.x
    • Chaos Testing (toxiproxy)

    • fsync=off for PostgreSQL

    • Drop Netty transport

    • Rework shading
    Mar, 2019
    @bsideup

    View Slide

  71. 1.12.x
    Jul, 2019
    @bsideup

    View Slide

  72. 1.12.x
    • dependsOn
    Jul, 2019
    @bsideup
    @Rule
    KafkaContainer kafka = new KafkaContainer();
    @Rule
    SchemaContainer schemaRegistryContainer = new SchemaContainer()
    .withKafka(kafka)
    .dependsOn(kafka);

    View Slide

  73. 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:

    View Slide

  74. 1.12.x
    • dependsOn

    • Improved pull handling

    • Azure Pipelines for Windows testing
    Jul, 2019
    @bsideup

    View Slide

  75. 1.12.x
    • dependsOn

    • Improved pull handling

    • Azure Pipelines for Windows testing
    Jul, 2019
    @bsideup

    View Slide

  76. 1.12.x
    • dependsOn

    • Improved pull handling

    • Azure Pipelines for Windows testing

    • DB2 and RabbitMQ modules
    Jul, 2019
    @bsideup

    View Slide

  77. Roadmap

    View Slide

  78. new DSL

    View Slide

  79. What’s wrong with the current one?

    View Slide

  80. KafkaContainer kafka = new KafkaContainer()
    .withLogConsumer(new Slf4jLogConsumer(log))
    .withEmbeddedZookeeper()
    .withEnv("FOO", "BAR")
    .withStartupAttempts(5);
    Current DSL
    @bsideup

    View Slide

  81. KafkaContainer kafka = new KafkaContainer()
    .withLogConsumer(new Slf4jLogConsumer(log))
    .withEmbeddedZookeeper()
    .withEnv("FOO", "BAR")
    .withStartupAttempts(5);
    Current DSL
    GenericContainer
    @bsideup

    View Slide

  82. KafkaContainer kafka = new KafkaContainer()
    .withLogConsumer(new Slf4jLogConsumer(log))
    .withEmbeddedZookeeper()
    .withEnv("FOO", "BAR")
    .withStartupAttempts(5);
    Current DSL
    KafkaContainer
    @bsideup

    View Slide

  83. KafkaContainer kafka = new KafkaContainer()
    .withLogConsumer(new Slf4jLogConsumer(log))
    .withEmbeddedZookeeper()
    .withEnv("FOO", "BAR")
    .withStartupAttempts(5);
    Current DSL
    GenericContainer
    @bsideup

    View Slide

  84. KafkaContainer kafka = new KafkaContainer()
    .withLogConsumer(new Slf4jLogConsumer(log))
    .withEmbeddedZookeeper()
    .withEnv("FOO", "BAR")
    .withStartupAttempts(5);
    Current DSL
    GenericContainer
    @bsideup

    View Slide

  85. 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

    View Slide

  86. public class KafkaContainer extends GenericContainer {}
    public class GenericContainer> /* */ {}
    @bsideup

    View Slide

  87. public class KafkaContainer extends GenericContainer {}
    public class GenericContainer> /* */ {}
    https://youtrack.jetbrains.com/issue/KT-17186
    https://stackoverflow.com/questions/39163749/how-to-use-a-java-self-bounded-class-in-scala
    @bsideup

    View Slide

  88. ❌ Hard to maintain
    @bsideup

    View Slide

  89. ❌ Hard to maintain
    ❌ Does not work with Kotlin/Scala
    @bsideup

    View Slide

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

    View Slide

  91. ❌ Hard to maintain
    ❌ Does not work with Kotlin/Scala
    ❌ Externally mutable objects
    ❌ “setX” isn’t supported, only “withX” (think collections)
    @bsideup

    View Slide

  92. ❌ 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  96. 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

    View Slide

  97. 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

    View Slide

  98. ✓ Super easy to maintain
    @bsideup

    View Slide

  99. ✓ Super easy to maintain
    ✓ Works with any JVM language
    @bsideup

    View Slide

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

    View Slide

  101. ✓ Super easy to maintain
    ✓ Works with any JVM language
    ✓ “Controllable mutability” - no modifications outside of
    “initialize”
    ✓ Void-retuning “setX” can easily be used
    @bsideup

    View Slide

  102. ✓ 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

    View Slide

  103. GraalVM focus

    View Slide

  104. @bsideup
    https://medium.com/graalvm/using-testcontainers-from-a-node-js-
    application-3aa2273bf3bb

    View Slide

  105. @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

    View Slide

  106. @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)));

    View Slide

  107. “container core”

    View Slide

  108. Testcontainers 1.x architecture
    Core

    (Docker, GenericContainer, 

    JUnit 4 int, test lifecycle)
    @bsideup

    View Slide

  109. Testcontainers 1.x architecture
    Core

    (Docker, GenericContainer, 

    JUnit 4 int, test lifecycle)
    JUnit Jupiter int.
    @bsideup

    View Slide

  110. Testcontainers 1.x architecture
    Core

    (Docker, GenericContainer, 

    JUnit 4 int, test lifecycle)
    JUnit Jupiter int.
    Modules like MySQL/Kafka/…
    @bsideup

    View Slide

  111. Testcontainers 2.x architecture
    Container-core

    (GenericContainer)
    @bsideup

    View Slide

  112. Testcontainers 2.x architecture
    Container-core

    (GenericContainer)
    Test Frameworks
    JUnit 4
    JUnit Jupiter
    Scala test?

    @bsideup

    View Slide

  113. 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

    View Slide

  114. 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

    View Slide

  115. View Slide

  116. Usability
    Flexibility
    Speed
    Features
    @bsideup

    View Slide

  117. Usability
    Flexibility
    Speed
    Features
    @bsideup

    View Slide

  118. Usability
    Flexibility
    Speed
    Features
    @bsideup

    View Slide

  119. Please welcome…

    View Slide

  120. Reusable
    containers

    View Slide

  121. Reusable
    containers

    View Slide

  122. Demo

    View Slide

  123. @bsideup

    View Slide

  124. ✓ Ultra-fast ITDD (Integration Test Driven Development)
    @bsideup

    View Slide

  125. ✓ Ultra-fast ITDD (Integration Test Driven Development)
    ✓ Minimal effort for the users
    @bsideup

    View Slide

  126. ✓ Ultra-fast ITDD (Integration Test Driven Development)
    ✓ Minimal effort for the users
    ✓ Works for most of the containers
    @bsideup

    View Slide

  127. ✓ 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

    View Slide

  128. ✓ 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

    View Slide

  129. 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

    View Slide

  130. @bsideup bsideup

    View Slide