엄범

 

유닛테스트에서는 DB를 안쓰거나, 간단한 쿼리 정도 사용하게 되므로 In-Memory 사용해도 무방하지만
복잡한 쿼리를 포함하여 실행하는 통합테스트는 In-Memory DB를 사용하면 아래와 같은 문제를 마주할 수 있다.

(근데 어차피 통합TC에서 리얼DB 사용할거라면 유닛테스트에도 리얼DB 사용하는게 속편하긴 하다)

 

운영에서는 다른 DB 쓰고, TC는 다른 DB(In-Memory) 사용할 때의 문제점

운영 DB는 Oracle 쓰고 있었는데, TC나 오프라인 환경에서는 H2를 사용하게끔 변경했다.

 

그러나... 다음과 같은 문제점이 있음.

  • 운영 DB와 In-Memory DB의 문법이 100% 호환 되지 않는다.
    • `` sysdate - ?`` 같은건, 오라클에서는 그냥 사용 가능하지만 H2에서는 타입 추론이 안돼서 ?를 CAST 해주어야 한다.
    • `` SUBSTRB`` 함수는 오라클에만 존재한다. H2에서는 `` CREATE ALIAS``로 직접 자바로 짜주면 되긴 함...
    • 이 밖에도 오라클에서는 되고 H2에서는 안되는 문법을 꽤나 높은 확률로 마주치게 된다. pagination 이라던가? Oracle Compatibility Mode 란게 있긴 한데, 좀 미흡하다
    • 오라클에서만 되는 문법을 다 걷어내고 호환되도록 짜는 것은 어렵고, 넌센스이며, 절대 H2로 바꾸기 어려울 것 같은 코드도 마주칠 수 있다. 그렇다고 쿼리를 이중으로 짜는건 너무 비효율적이다.
  • In-Memory DB에서 잘 넘어갔는데, 운영 DB에서는 안될 수 있다. 이런 케이스를 마주칠 가능성이 분명 존재한다.
    • = TC를 신뢰할 수 없다. 
    • 이건 문법의 문제라기 보단 내장함수의 동작 같은 부분에서 차이가 있을 수 있기 때문.
  • 오히려 이런 문제들 때문에 H2 썼을 때 공수가 더 드는 것 같다. (잘 돌아가는데... H2를 위해서 운영 쿼리를 바꿔야 한다? 좀 그렇다...)

 

더 좋은 방법은 없나?

더 좋은 방법은, 사실 In Memory DB를 사용하여 얻고자 하는 것이 "고립된 데이터 상태에서의 테스트" 이므로 기존의 Oracle이나 MySql을 이용해 고립된 데이터 상태를 만들 수 있다면 문제가 해결된다.

```

테스트 DB 만들고  ->  [DDL.sql, DATA.sql] 로 초기화하고  ->  TC 돌리고  ->  DB 삭제

```

이런 사이클로 돌릴 수 있다면 굳이 H2 쓸 필요가 없는거 아닌가.

TestContainers 사용해서 아예 DB 올렸다 내렸다 하는 식으로 가능할거고... 시간은 좀 오래걸리겠지만, 이득이 훨씬 큰 듯.

 

로컬에서만 H2 사용해서 돌린다?
  • 그럼 로컬에서는 통합 TC를 안돌려보겠다는 의미가 됨. PR 올려서 CI 빌드 할 때만 통합 TC를 돌려볼 수 있을텐데... 이건 너무 비효율이다.
  • 빌드 시에도 로컬에도 docker 올려서 리얼DB와 같은 DB로 통합 TC 돌리는게 낫다고 봄.

 

암튼 정리해보면.

 

1. TestContainers를 사용한 방식은 매번 DB 올렸다 내렸다 해야 하니 시간은 좀 오래 걸릴지 몰라도, "고립된 데이터 상태"를 만들 수 있으므로 제일 괜찮아 보인다.

  • TestContainers를 사용한 방법과 Gradle | Maven 빌드 스크립트에 포함하는 방법이 있음.
  • 매번 올렸다 내렸다 하는건 `` -DreuseDatabase`` 플래그 이용해서 해결 가능한 듯.
  • DDL이나 초기 데이터는 flyway로 관리

 

2. 그 다음으로 괜찮은건 docker 이용해서 local에 아예 DB 올려두고 쓰는 것. 이건 롤백이 좀 귀찮을 수 있다. (Spring Batch)

  • 근데 CI 서버에서 돌아가는 TC에 대한 DB도 필요하므로 CI 서버에도 DB를 올려야 한다는게 문제.
  • CI 서버에 DB 안올리고 alpha DB를 사용한다면, alpha QA 동안 적재된 데이터 등등과 섞임. 
  • 따라서 알파 DB를 사용할거라면, CI Test 수행 시 새 테이블스페이스를 생성하고 끝나면 삭제. 하는 식으로 알파 데이터와 분리하면 가능할 듯.
  • 테이블 스키마, 인덱스 등 복사는 (1) 처럼 flyway로 관리해도 되고... 아예 알파DB만 사용한다면 알파DB 스키마를 복사해와도 됨.
    • 오라클의 경우 expdp,impdp 명령어 또는 프로시저  

 

3. H2 사용한 방법은 상기한 문제가 있으므로 논외.

 

참고

과연 In Memory DB가 TC에 더 효과적인가?? 에 대한 의문이 들어서. 좀 찾아봤다.

 

  • github.com/dotnet/efcore/issues/18457  
    • efcore라는 DB mapper에서 `` InMemoryProvider``를 제거하느냐 마느냐에 대한 논쟁으로 이 것에 대해서는 의견이 분분하지만, 대략 필요한 부분을 종합해보면
    • 1. 유닛 테스트에서는 InMemory DB가 충분히 유용하다.
    • 2. 통합 테스트에서는 당연히 리얼 DB 써야한다.
  • jimmybogard.com/avoid-in-memory-databases-for-tests/ 
    • Unit Test에서도 그닥 이점이 없다. 라고 꽤나 강하게 얘기하고 있는데... 이건 efcore에서 In-Memory DB가 리얼DB를 흉내내도록 하는 것에 대한 어려움이 포함되어 있기 때문에 사견이 좀 강하게 들어간 것 같고...
    • 아무튼 이 사람도 유닛 테스트에는 InMemory DB 쓰더라도, integration test에는 무조건 리얼 DB 써야 된다고 있음.
  • 👍   phauer.com/2017/dont-use-in-memory-databases-tests-h2/
    • 내가 했던 고민이랑 거의 비슷한 내용.
    • Solution: Throw H2 and Fongo away and use a dockerized version of your real database instead. Docker simplifies the management of database instances.

 

TestContainer 이용할건데, 설정을 어떻게 구성하면 좋나?

  • 명시 안해도 자동으로 @SpringBootTest 테스트 실행 시 마다 독립 container DB 사용하도록 설정.
    • Query를 날리려면 Mapper를 DI 받아야 하므로 어차피 SpringBootTest여야 함. (Spring과 무관한 Unit Test는 X) 
    • MyBatis가 test container 사용하도록 하려면, test container와 연결된 dataSource bean 필요.
    • 각각의 테스트클래스에서 DB 설정하지 않아도 되게끔. 최소 annotation이나 interface로 분리.
  • test container 사용 안하고 디버깅 목적으로 로컬DB, 알파DB 사용해서 돌리고 싶다면, 간단하게 변경 가능할 것.

 

관련 링크