모노리스 아키텍처에서 마이크로 서비스로 마이그레이션이 많아짐에 따라 테스트에 대한 내용도 달라지고 있다.
모노리스에서는 아래와 같은 삼각형 형태의 테스트 분포가 일반적이다.
단위테스트가 제일 많고, integraion, integrated 순으로 분포된다.
마이크로 서비스는 몇개의 단위테스트 만으로도 전체 어플리케이션의 로직을 커버할 수 있을 만큼 단위가 작다(이론적으로).
하지만 외부 dependency가 커짐에 따라 integration 테스트의 양은 증가한다.
따라서 테스트의 분포는 아래와 같은 육각형 형태가 된다.
Microservice 아키텍처로 인해 통합테스트의 비율이 커지고 있다.
Mocks를 이용한 테스트도 좋지만, 실제로 통합테스트를 하기 전까지는 코드가 실제로 작동하는지 알 수 없다.
Testcontainers를 이용하면 프로그래밍을 통해 통합테스트에 필요한 외부 dependencies를 마련할 수 있다.
Testcontainers 와 Docker 덕분에 유래없이 통합테스트를 쉽게 구현할 수 있다.
pom.xml에 dependency들을 추가한다.
데모에 사용되는 어플리케이션은 GameApp으로 wallet 정보는 postgres에, user와 items는 couchbase에 저장한다.
Test 클래스에 "@Testcontainers" 어노테이션을 추가한다.
"@Container" 어노테이션은 테스트 첫 실행시 postgres와 couchbase 컨테이너를 생성하고, 해당 저장소는 모든 테스트들이 공유한다.
"@DynamicPropertySource"를 통해 properties를 정의할 수 있다.
Testcontainers는 random port를 할당하기 때문에, port 설정은 신경쓰지 않는다.
테스트 실행시 아래와 같이 postgres와 couchbase container가 생성되고, 테스트가 끝나면 사라진다.
하나의 microservice에 postgres와 couchbase 두개의 database를 사용한 것은 microservice에 어울리지 않는다.
두개의 microservice로 나누어 보자. 하나는 wallet를 저장하는 postgres를 사용하고, 다른 하나는 couchbase를 이용해 game user를 저장한다.
소스를 분리하고, wallet microservice의 도커 이미지를 만들어, dockerhub의 denisros/wallet_mciroservice 레포지토리에 푸쉬하였다.
테스트에서는 game user를 만들고, wallet microservice를 호출하여 해당 user의 wallet를 생성한다.
wallet microservice 와 postgres 사이에 통신이 가능해야 하므로, network 를 생성한다.
wallet microservice를 GenericContainer를 이용해 생성하고, 연결할 postgres의 DATASOURCE_URL를 postgres container의 network alias를 이용해 설정한다.
같은 network를 통해 직접 접근하므로, 5432 디폴트 postgres포트를 이용한다.
walletMicroservice는 8080을 expose하는데, 테스트시 host와 매핑되는 랜덤포트를 얻기위해 walletMicroservice.getMappedPort(8080)를 이용한다.
테스트 실행 시 wallet microservice, postgres, couchbase 컨테이너 역시 실행되는 것을 확인할 수 있다.
데모는 단순하지만, 복잡한 시나리오에서도 역시 testcontainers를 사용할 수 있다.
microservice간의 통신은 가능한 asynchronous 하게 한다. 예를 들면 kafka topic를 이용한다. Testcontainers에서는 kafka를 지원한다.