Integration Test Using TestContainers

Spring Boot 와 RDBMS 환경에서 개발하면서 Test 를 작성할 때, 테스트 환경의 경우 주로 H2 를 사용한다. H2 는 몇 줄만 추가해주면 Test 환경에서 사용이 가능하기 때문에 설정이 매우 쉽다. 하지만 production 환경에서는 MySQL, PostgreSQL, MS SQL, Oracle 등 H2 와는 다른 Database 를 사용하고 있다면 이슈가 발생할 수 있다.

기본적으로 지원되는 문법이 다르거나 Database Engine 이 동작하는 방식이 달라서 Test 를 신뢰할 수 없다. 이것은 본질적으로 Test Code 를 작성하는 의미를 지키기 힘들어진다.

H2 에서 compatible 모드를 지원하지만 본질적으로 완전하게 동일한 환경을 보장할 수 없다. 

아래 링크를 통해서 확인할 수 있다.

http://www.h2database.com/html/features.html#compatibility

모든 database engine 들은 조금씩 다르게 동작합니다. H2 는 ANSI 표준을 지원하고 다른 database 와 호환되도록 노력합니다. 하지만 여전히 차이가 존재합니다.

https://www.h2database.com/html/advanced.html#acid



또한, Transaction 도 다르게 동작한다. ACID 의 개념을 빠뜨릴 수 없는데 MySQL 의 경우 default Isolation Level 은 REPEATABLE READ 이지만 H2 default Isolation Level 은 READ COMMITED 이다. Phantom Read 이슈가 발생하게 된다. 

물론 설정을 REPEATABLE READ 로 변경하고 Production 환경과 최대한 비슷하게 설정하고나서 H2 사용을 고려해 볼 수 있다. 하지만, 쉽게 설정할 수 있다는 H2 의 장점이 없어진다.

따라서 Production 환경과 최대한 동일한 환경으로 쉽고 빠르게 설정하여 테스트를 진행할 수 있도록 TestContainers 를 사용하자.


TestContainers 는 Java library 이다. Integration test 를 복잡한 설정 없이 Docker container 를 통해서 빠르고 쉽게 할 수 있다. RDBMS 뿐만 아니라, Kafka, Nginx 등 Message Queue, Web Server 그리고 Custom Image 나아가 docker-compose 또한 사용할 수 있다.

그렇다면 이제 간단하게 기본 동작 방식과 간단한 팁을 예제를 통해서 확인해보자.

Local 개발환경은 docker compose 를 이용하여 MySQL 5.7 version 의 container 를 사용한다고 가정한다. 한글과 이모지를 사용할 수 있도록 character-set 과 collation 을 utf8mb4 로 설정해주고, init.sql 파일을 이용하여 처음 schema 와 기본 data 를 load 할 수 있도록하기 위해서 skip-character-set-client-handshake 커맨드를 사용했다.


Local 개발환경 profile yaml 파일이다.



기본적인 MySQL 기반 JPA Spring Boot Application 을 아래와 같이 만들었다.

그리고 testcontainers 의존성을 추가 해준다. junit-jupiter, mysql


Writer 1 : N Book 관계의 Entity 로 Book 에서 Writer 로 단방향 관계를 맺어주었다.

그리고 간단하게 Service, Repository 작성하였다.


아래와 같이 testcontainers 를 이용하여 Integration test code 를 작성했다. 



test 환경 profile yaml 파일이다.


Jupiter / Junit5  2 가지 동작 방식

- 모든 test method 마다 재시작되는 container
- 하나의 test class 의 모든 test method 를 공유하는 container

static 키워드를 사용한 MySQL Container 는 class 안의 모든 test method 는 해당 Container 를 공유할 수 있다.
반면에 static 키워드가 없는 PostgreSQL Container 는 test method 마다 다른 Container 가 생성된다.


따라서, 하나의 test method 가 시작되기전에 container 가 시작되고 test method 가 동작하고 종료되면 해당 container 도 종료된다. 따라서 test method 가 많아질수록 container boot time 이 증가된다.

Reuse 를 통한 컨테이너 부트 타임 줄이기

Container 를 생성할 때 withReuse(true) 를 통해서 shouldBeReused 필드를 설정하여 컨테이너 재사용 여부를 설정할 수 있다. 예제에서 나오는 MySQLContainer 는 GenericContainer 를 상속하고 있다. GenericContainer 의 tryStart 메서드를 보면 shouldBeReused 필드와 TestcontainersConfiguration.getInstance().environmentSupportsReuse() 메서드를 통해서 재사용 여부를 확인하고 가능하다면 컨테이너 hash 값을 통해서 재사용을 할 수 있도록 한다.


TestcontainersConfiguration.getInstance().environmentSupportsReuse() 를 보면 아래와 같이 testcontainers.reuse.enable 의 값을 읽어서 재사용 여부를 판단하고 있다.
결론은 withReuse(true) 를 통해서 설정해주고 이와 동시에 test code 를 실행하는 host machine 의 ~/.testcontainer.properties 파일안에 testcontainers.reuse.enable 값을 true 로 설정해주어야한다.

Singleton Container Pattern

서비스가 성장함에 따라 기능이 많아지게 되면 Integration test 양도 당연히 많아진다. 이는 class 숫자가 증가하게 된다. testcontainers 를 사용할 때 가장 유의할 점이 boot time 관리이다.

추상 클래스 활용

abstract class 를 작성하고 상속을 통해서 여러 class 가 오직 하나의 container 를 사용하도록 할 수 있다.



인터페이스 활용

상속의 가장 큰 문제점은 다이아몬드 상속 문제이다. 따라서 Java 에서는 다중상속을 할 수 없다. 이로인해 발생할 수 있는 문제점은 상속을 받은 자식은 부모의 모든 기능을 상속받지 않고 싶을 수 있다. 예를 들어, 부모 클래스에서 MySQL, Kafka, Nginx Container 를 관리하고 있다고 가정해보자. 자식 클래스는 MySQL, Kafka Container 만을 사용하여 Integration Test 진행해야 한다면 Nginx Container 는 낭비가 된다. 이러한 문제를 Interface 를 활용하여 해결할 수 있다.




마지막으로 테스트 성공 결과로 이글을 마무리하겠다.



 참고

https://github.com/ndgndg91/testcontainers-demo

http://www.h2database.com/html/features.html#compatibility

https://www.h2database.com/html/advanced.html#acid

https://www.testcontainers.org/

https://www.testcontainers.org/test_framework_integration/junit_5/

https://github.com/testcontainers/testcontainers-java/blob/master/core/src/main/java/org/testcontainers/containers/GenericContainer.java

https://github.com/testcontainers/testcontainers-java/blob/7b098bc6bbdf18cf1c212337ba3e39afef6212a5/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java#L167

https://github.com/testcontainers/testcontainers-java

https://www.youtube.com/watch?v=v3eQCIWLYOw

https://bsideup.github.io/posts/local_development_with_testcontainers/

https://devs0n.tistory.com/24

https://pawelpluta.com/optimise-testcontainers-for-better-tests-performance/

https://medium.com/pictet-technologies-blog/speeding-up-your-integration-tests-with-testcontainers-e54ab655c03d

댓글

이 블로그의 인기 게시물

About JVM Warm up

About idempotent

About Kafka Basic

About ZGC

sneak peek jitpack

Spring Boot Actuator readiness, liveness probes on k8s

About Websocket minimize data size and data transfer cost on cloud

About G1 GC

대학생 코딩 과제 대행 java, python, oracle 네 번째