Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Testcontainers - Geekout EE 2017 presentation

799 views

Published on

Unit testing our code on the JVM is well catered for with a lot of great tools that are mature and reliable – things tend to just work. Integrated testing, however, is another matter. Any time we face a situation where we need to involve non-JVM elements in our tests, we’re faced with painful environment setup and repeatability issues. Testcontainers aims to make integrated tests a little less unpleasant, through the power of Docker. Databases, Web browsers – in fact anything available as a Docker image – can be made available as a component to use in our tests.
In this talk, we’ll go through the motivations for building Testcontainers, its features, as well as some examples of using it in practice for testing various types of components.

Published in: Technology
  • Be the first to comment

Testcontainers - Geekout EE 2017 presentation

  1. 1. Who am I? Richard North • Lifelong geek • Java/iOS/web/'devops' - 'full stack' • Ex-consultant, now at Skyscanner • UK and Japan • Proud father of two! @whichrich
  2. 2. Testing Quick poll
  3. 3. Who writes tests?
  4. 4. Who writes unit tests?
  5. 5. Who writes integrated tests? Code under test Test suite 'External'/out-of-process dependencies
  6. 6. Who loves creating/maintaining integrated tests?
  7. 7. Integrated tests !
  8. 8. "You need to manually install Oracle XE on your dev machine to run the tests..." Weakly defined dependencies
  9. 9. "Oops Firefox upgraded itself - nobody can run Selenium tests today" Bit rot - variance with time
  10. 10. "Test B always fails if Test A ran before it" Shared state - tests interfere with each other
  11. 11. "But it worked when we tested against the mock!" Mocks provide varying assurance of compatibility
  12. 12. "I can only run that test on CI. I can't connect a debugger." Testing capabilities vary through the pipeline
  13. 13. There has to be a better way
  14. 14. Testing Pyramid
  15. 15. Just don't have any integrated tests?
  16. 16. "2 Unit Tests, 0 Integration Tests"
  17. 17. Be pragmatic
  18. 18. Have as few integrated tests as we can get away with ...
  19. 19. Have as few integrated tests as we can get away with and make them easier to work with
  20. 20. Deployment and Infrastructure
  21. 21. Once upon a time...
  22. 22. "It'll take you three days to build an environment" — My first tech lead
  23. 23. - Slow, manual setup - No dev/prod parity - Expensive to do the right thing
  24. 24. Deployment and Infrastructure !
  25. 25. Fast forward to the future
  26. 26. "we can build an environment in seconds" - quickly, cheaply - always the same - definition is version controlled - Docker Hub - plentiful base images
  27. 27. Deployment ❤ Docker
  28. 28. What if..? Docker for Testing?
  29. 29. Testcontainers • Manage Dockerized external dependencies via a Java object facade • JUnit integration - Starts/stops containers for each class/method • Reliability: • start from clean state • isolated instances • port randomisation • tag-based versioning • Java JUnit support; also Spock, Scala and Python wrappers/forks
  30. 30. Testcontainers project • Initial versions mid 2015 • 36 contributors over time; Sergei Egorov (@bsideup) is the main co-maintainer • Some users: • ZeroTurnaround • Spring Data • Apache • Zalando • Alfalab • Zipkin • Others...!
  31. 31. Where can Testcontainers help me?
  32. 32. Supported test dependencies Type Specialisations GenericContainer Any image on Docker Hub (or private repo!) Databases MySQL, PostgreSQL, MariaDB, Oracle XE, DynamoDB Selenium Chrome, Firefox Docker Compose Anything in a Docker Compose file Dockerfile / Dockerfile DSL Anything expressable in a Dockerfile
  33. 33. A simple example Integrated tests involving a cache
  34. 34. public interface Cache { void put(String key, String value); String get(String key); } public class RedisBackedCache implements Cache { // Uses Redis... }
  35. 35. public class RedisBackedCacheTest { private Cache cache; @Before public void setup() { cache = new RedisBackedCache("localhost", 6379); } @Test public void testGetAndSetAValue() { cache.put("foo", "bar"); final String retrievedValue = cache.get("foo"); assertEquals("The retrieved value is the same as the inserted value", "bar", retrievedValue); } }
  36. 36. public class RedisBackedCacheTest { @Rule public static GenericContainer redis = new GenericContainer("redis:3.2.8"); private Cache cache; @Before public void setup() { cache = new RedisBackedCache(???, ???); } @Test public void testGetAndSetAValue() { cache.put("foo", "bar"); final String retrievedValue = cache.get("foo"); assertEquals("The retrieved value is the same as the inserted value", "bar", retrievedValue); } }
  37. 37. public class RedisBackedCacheTest { @Rule public static GenericContainer redis = new GenericContainer("redis:3.2.8") .withExposedPorts(6379); private Cache cache; @Before public void setup() { cache = new RedisBackedCache(redis.getContainerIpAddress(), redis.getMappedPort(6379)); } @Test public void testGetAndSetAValue() { cache.put("foo", "bar"); final String retrievedValue = cache.get("foo"); assertEquals("The retrieved value is the same as the inserted value", "bar", retrievedValue); } }
  38. 38. What will Testcontainers do here?
  39. 39. • Automatic discovery of local docker environment • Pull images or build from Dockerfile • Start/stop container • Wait for it to be ready (log string / listening port / protocol- specific) • Port mapping • Clean up
  40. 40. What have we avoided? • No need to install the dependency • No need to keep it running, or make sure it's running • No concern over version or configuration differences • No differences between what runs on CI and locally • No port clashes, no shared state unless we want it
  41. 41. What have we gained? • Repeatability - locked version redis:3.2.8 • Debuggable locally - runnable in IDE • Parallelizable • Runs anywhere that Docker runs
  42. 42. 'Anywhere Docker runs' Automatic discovery: • Docker for Mac and Docker for Windows • Docker Machine • Uses a running machine instance, or default • Automatically starts up Docker Machine if needed • Docker on Linux • Cloud CI • Travis CI • Circle CI • Docker in Docker • ... or wherever DOCKER_HOST is set
  43. 43. Example 2 Database testing
  44. 44. A simple DAO API public interface UsefulDao { void putAThing(String name, SomeObject value); SomeObject getAThingByJsonId(Integer id); }
  45. 45. A corresponding test public class UsefulDaoTest { private UsefulDao dao; @Before public void setUp() throws Exception { // Instantiate the DAO // Connect // Create schema and data } @Test public void testPutAndGetByJsonIndex() throws Exception { // INSERT and SELECT something } }
  46. 46. How can we test this with no database? Embedded database!
  47. 47. Our (fictitious) schema CREATE TABLE THINGS ( name VARCHAR(255), data JSONB );
  48. 48. JSONB is a PostgreSQL data type - how can we test this? • Embedded database? • Run PostgreSQL through our build script? • Hope that the developer/CI environment has the right version of PostgreSQL? • Give up, and don't use database features we can't easily test? ! • Don't test interactions with the DB, and hope that it works in prod? !! • Can we use Testcontainers..?
  49. 49. Yes we can! @Rule public PostgreSQLContainer postgres = new PostgreSQLContainer("postgres:9.6.2"); Access at test-time postgres.getJdbcUrl(); // Unique URL for a container instance postgres.getUsername(); postgres.getPassword();
  50. 50. Example 3 Selenium Webdriver testing
  51. 51. public class SeleniumTest { private WebDriver driver; @Before public void setUp() throws Exception { // Connect to remote selenium grid // or start a local browser process (Headless? Real browser?) } @Test public void testSimple() throws IOException { ... } }
  52. 52. public class SeleniumTest { @Rule public BrowserWebDriverContainer chrome = new BrowserWebDriverContainer() .withDesiredCapabilities(DesiredCapabilities.chrome()); private WebDriver driver; @Before public void setUp() throws Exception { driver = chrome.getWebDriver(); } @Test public void testSimple() throws IOException { ... } }
  53. 53. driver.get("https://2017.geekout.ee/"); driver.findElement( By.linkText("SCHEDULE")) .click(); driver.findElement( By.partialLinkText("TestContainers")) .click(); driver.findElement( By.linkText("Richard North")) .click(); final String siteUrl = driver.findElement( By.partialLinkText("testcontainers")) .getText(); assertEquals("The right link is found", "https://www.testcontainers.org/", siteUrl);
  54. 54. Recording videos @Rule public BrowserWebDriverContainer chrome = new BrowserWebDriverContainer() .withDesiredCapabilities(DesiredCapabilities.chrome()) .withRecordingMode(RECORD_FAILING, new File("./target"));
  55. 55. Debug via VNC! Set a breakpoint: Get a VNC URL chrome.getVncAddress() // e.g. "vnc://vnc:secret@localhost:32786" Connect $ open vnc://vnc:secret@localhost:32786
  56. 56. Recap so far • Using an image from Docker Hub as a dependency • Specialised database support • Selenium testing
  57. 57. Example 4 Dockerfile build
  58. 58. Build a container image at test time Allows: • Running code in real, prod-like Docker image • Create an image that's not available from a registry • Parameterized builds - using a DSL Doesn't require a separate phase for build/test pipeline
  59. 59. Build a container image at test time - Dockerfile FROM tomcat:8.5.15 COPY service.war /usr/local/tomcat/webapps/my-service.war
  60. 60. Build a container image at test time - Dockerfile @Rule public GenericContainer server = new GenericContainer( new ImageFromDockerfile() .withFileFromFile("Dockerfile", new File("./Dockerfile")) .withFileFromFile("service.war", new File("target/my-service.war"))) .withExposedPorts(8080); @Test public void simpleTest() { // do something with the server - port is server.getMappedPort(8080)); }
  61. 61. Build a container image at test time - DSL @Rule public GenericContainer server = new GenericContainer( new ImageFromDockerfile() .withFileFromFile("service.war", new File("target/my-service.war")) .withDockerfileFromBuilder(builder -> { builder .from("tomcat:8.5.15") .copy("service.war", "/usr/local/tomcat/webapps/my-service.war") .build(); })) .withExposedPorts(8080); @Test public void simpleTest() { // do something with the server - port is server.getMappedPort(8080)); }
  62. 62. Example 5 Docker Compose
  63. 63. docker-compose.backend.yml: version: '2' services: db: image: mongo:3.0.15 cache: image: redis:3.2.8 search: image: elasticsearch:5.4.0
  64. 64. Multiple containers as JUnit rules One way? public class SimpleSystemTest { @ClassRule public GenericContainer db = new GenericContainer("mongo:3.0.15"); @ClassRule public GenericContainer cache = new GenericContainer("redis:3.2.8"); @ClassRule public GenericContainer search = new GenericContainer("elasticsearch:5.4.0"); // ... tests ... }
  65. 65. Using Docker Compose during a test @Rule public DockerComposeContainer backend = new DockerComposeContainer(new File("./docker-compose.backend.yml")) .withExposedService("db", 27017) .withExposedService("cache", 6379) .withExposedService("search", 9200); @Test public void simpleTest() { // obtain host/ports for each container as follows: // backend.getServiceHost("db", 27017); // backend.getServicePort("db", 27017); // ... }
  66. 66. Docker Compose in Testcontainers • Unique, random, name prefix and isolated network - allows concurrent usage One usage mode: • Use docker-compose up -f ... during local dev (overrides file to expose ports) • Run tests concurrently via Testcontainers without stopping local environment • Seamless transition to CI - use Testcontainers
  67. 67. Summary • Generic image container • Specialised Database container • Selenium containers with video recording and VNC debugging • Building a Dockerfile • Docker Compose
  68. 68. What's next?
  69. 69. Speed enhancements • Startup containers in in advance • Checkpoint-Restore In Userspace
  70. 70. 'Version 2' • API tidyup • decouple from JUnit 4 and support other frameworks directly
  71. 71. Core elements as a library • high-level Docker object API as a library, for more than just testing usage • planning collaboration with Arquillian Cube project team !
  72. 72. More things! • Pumba (Chaos testing) - landing soon! • ...
  73. 73. Conclusion • Hopefully another useful tool for your testing toolbox • Easy to use, powerful features for many scenarios • Please try it out yourselves!
  74. 74. Thanks to • Everyone who has contributed to the project • ZeroTurnaround • You!
  75. 75. testcontainers.org github.com/testcontainers @testcontainers
  76. 76. Thank you!

×