How to make a world-class integration testing library?

A presentation at DevXConf in May 2022 in by Oleg Šelajev

Slide 1

Slide 1

How to make a world-class integration testing library?

Slide 2

Slide 2

testcontainers.org

Slide 3

Slide 3

Slide 4

Slide 4

Oleg Šelajev Developer relations @shelajev github.com/shelajev oleg@atomicjar.com

Slide 5

Slide 5

Model: record Rating (String talkId, Integer value) {}

Slide 6

Slide 6

Downloads

Slide 7

Slide 7

Unique IPs

Slide 8

Slide 8

e2e Integration tests Unit tests By Unknown author - U.S. Army Photo, Public Domain, https://commons.wikimedia.org/w/index.php?curid=55124

Slide 9

Slide 9

https://labs.spotify.com/2018/01/11/testing-of-microservices/

Slide 10

Slide 10

Slide 11

Slide 11

Slide 12

Slide 12

Is this the goal?

Slide 13

Slide 13

You’re given tools but they don’t work reliably or are di cult to use to solve problems, you don’t know exist ffi fi slow, joyless, or signi cantly behind the state of the art

Slide 14

Slide 14

—version: ‘2’ services: zookeeper: image: con uentinc/cp-zookeeper:7.0.1 ports: - “32181:32181” environment: ZOOKEEPER_CLIENT_PORT: 32181 ZOOKEEPER_TICK_TIME: 2000 kafka: image: con uentinc/cp-kafka:7.0.1 ports: - “29092:29092” depends_on: - zookeeper environment: KAFKA_BROKER_ID: 1 KAFKA_ZOOKEEPER_CONNECT: zookeeper:32181 KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:29092 fl fl KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR :1

Slide 15

Slide 15

What does winning look like?

Slide 16

Slide 16

Replacing a hard problem with a hard solution is not success. Nor is replacing an easy problem with an easy solution.

Slide 17

Slide 17

Reliability

Slide 18

Slide 18

Trustworthyness Shifting failures left Errors making sense Sensible defaults Developers are people too!

Slide 19

Slide 19

System checks Checking the system… ✔ Docker version should be at least 1.6.0 ✔ Docker environment should have more than 2GB free disk space ✔ File should be mountable ℹ︎ ✔ A port exposed by a docker container should be accessible

Slide 20

Slide 20

During After

Slide 21

Slide 21

The problem Extra mile Port conflicts Port randomization Hard-coded scenarios Data-driven tests Slow tests Parallelization

Slide 22

Slide 22

Technology radar We think it’s a useful default option for creating a reliable environment for running tests. … Our teams have consistently found this library of programmable, lightweight and disposable containers to make functional tests more reliable. https://www.thoughtworks.com/en-us/radar/languages-and-frameworks/testcontainers

Slide 23

Slide 23

Slide 24

Slide 24

Slide 25

Slide 25

The simplest way JDBC driver magic: spring.datasource.url=jdbc:tc:postgresql:14-alpine: testcontainers/database With a schema initialization: jdbc:tc:mysql:5.7.34: init_mysql.sql databasename?TC_INITSCRIPT=file:src/main/resources/ With additional con g: / databasename?TC_TMPFS=/testtmpfs:rw / / / / / / / fi jdbc:tc:postgresql:9.6.8:

Slide 26

Slide 26

The ecosystem of modules KafkaContainer kafka = new KafkaContainer( DockerImageName.parse(“confluentinc/cp-kafka”).withTag(“5.4.3”) ) public ToxiproxyContainer toxiproxy = new ToxiproxyContainer(DockerImageName.parse(“shopify/toxiproxy:2.1.0”)) .withNetwork(network) .withNetworkAliases(“toxiproxy”); PostgreSQLContainer<?> postgreSQLContainer = new PostgreSQLContainer (DockerImageName.parse(“postgresql:14-alpine”));

< K3sContainer k3s = new K3sContainer( DockerImageName.parse(“rancher/k3s:v1.21.3-k3s1”)) .withLogConsumer(new Slf4jLogConsumer(log));

Slide 27

Slide 27

testcontainers.org

Slide 28

Slide 28

Developer Experience Community Technical excellence

  • The technology

Slide 29

Slide 29

The generic API

< GenericContainer<?> redis = new GenericContainer (“redis:6-alpine”) .withExposedPorts(6379);

Slide 30

Slide 30

The generic API

Slide 31

Slide 31

Slide 32

Slide 32

If it’s not in the docs — it doesn’t exist!

Slide 33

Slide 33

The problem with docs (a) people will never read external docs; (b) people will rarely read inline docs/help strings/tooltips; (c) people will most likely only read the information that is shown to them

Slide 34

Slide 34

The IDE is your only friend!

Slide 35

Slide 35

Our clients’ clients love us

Slide 36

Slide 36

Private registries Local API: final PostgreSQLContainer<?> mysql = new PostgreSQLContainer ( DockerImageName.parse(“registry.mycompany.com/mirror/mysql:8.0.24”) .asCompatibleSubstituteFor(“mysql”) ); Con guration: TESTCONTAINERS_HUB_IMAGE_NAME_PREFIX=registry.mycompany.com/mirror/ Programmatic API:

< fi public abstract class ImageNameSubstitutor implements Function<DockerImageName, DockerImageName>

Slide 37

Slide 37

Chaos engineering public Network network = Network.newNetwork();

< public GenericContainer<?> redis = new GenericContainer (DockerImageName.parse(“redis:6-alpine”)) .withExposedPorts(6379) .withNetwork(network);

Slide 38

Slide 38

Chaos engineering public Network network = Network.newNetwork(); public GenericContainer<?> redis = new GenericContainer (DockerImageName.parse(“redis:6-alpine”)) .withExposedPorts(6379) .withNetwork(network);

< public ToxiproxyContainer toxiproxy = new ToxiproxyContainer(DockerImageName.parse(“shopify/toxiproxy:2.1.0”)) .withNetwork(network) .withNetworkAliases(“toxiproxy”);

Slide 39

Slide 39

Chaos engineering final ToxiproxyContainer.ContainerProxy proxy = toxiproxy.getProxy(redis, 6379); proxy.toxics() .latency(“latency”, ToxicDirection.DOWNSTREAM, 1_100) .setJitter(100); • • • • • • bandwidth - Limit a connection to a maximum number of kilobytes per second. latency - Add a delay to all data going through the proxy. The delay is equal to latency +/- jitter. slicer - Slices TCP data up into small bits, optionally adding a delay between each sliced “packet”. slowClose - Delay the TCP socket from closing until delay milliseconds has elapsed. timeout - Stops all data from getting through, and closes the connection after timeout limitData - Closes connection when transmitted data exceeded limit.

Slide 40

Slide 40

Reusable containers Extending the lifecycle of the containers Local API: static KafkaContainer kafka = new KafkaContainer() .withReuse(true); fi Con guration:

Slide 41

Slide 41

Slide 42

Slide 42

Changing local development git clone https://github.com/shelajev/ primes-kafka.git && cd primes-kafka ./mvnw quarkus:dev Clone & Run!

Slide 43

Slide 43

Slide 44

Slide 44