Post

좋은 테스트 코드를 위한 고민

좋은 테스트 코드를 위한 고민

경험에 따라 다르겠지만 테스트 코드는 아마도 주니어 레벨에서는 대부분 생소하기도 하고 간과하기 쉬운 것 같다.

alt text

다행이도 나는 이전 실무 경험을 통해 테스트 코드를 접해볼 수 있었고 심지어 꽤 많이 작성해보았다. 하지만 그럼에도 불구하고 좋은 테스트코드를 작성했다고는 감히 말할 수 없을 것 같다.

오늘은 좋은 테스트 코드를 어떻게 하면 작성 할 수 있을까라는 고민을 하게 되었고 고민 끝에 돈을 드려 유료강의를 구매해서 지식을 얻고자 했다. 최근에 스프링 기반 프로젝트를 만들어보고 싶어서 진행을 하다가 실제로 배포하고 운영단계까지 가보고 싶은 목표를 염두해두고 있어서 테스트 코드도 꼭 넣어보고 싶은 마음 덕분에 고민을 하다 인프런에서 강의를 구매해서 수강하였다. 아직 완강 전이지만 강의 내용이 흥미롭고 이전 경험을 토대로 회고가 되면서 와닿는 부분이 있어서 글을 작성하게 되었다.

강의는 인프런에 있는 김우근님의 “Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트” 입니다.

스프링 애플래케이션 테스트

기본적으로 스프링을 처음 시작하면 가장 쉽게 접할 수 있는 테스트가 in-memory H2 데이터베이스를 이용해서 실제로 영속성 컨텍스트를 이용해 DB 에 쿼리를 날려 CRUD 로직을 검증하는 식일 것 같다.

이 방법도 테스트가 아예 없는 것과 비교하면 큰 발전이지만 뭔가 어긋나있다는 느낌을 받았다. 이런식으로 별도의 테스트 DB 를 실행해 실제로 작업을 실행하는 테스트는 보통 integration test 또는 e2e test 라고 부를수 있을것같은데 그럼 unit test (단위 테스트) 즉 별도로 실행해야하는 의존성 없이 순수 자바 코드를 테스트하는 테스트 코드는 스프링에서 어떻게 작성해야 할까라는 의문을 품고 계속 자료를 찾아보았지만 단순 구글링을 통해 마땅히 내가 원하는 답을 얻을 수 없었다.

몇가지 강의들중 어떤걸 구매할지 고민이 되었는데 김우근님의 강의 설명란에 “H2 같은 테스트 DB가 없는 NoSQL에 테스트를 넣고 싶은 분” 이라는 문구가 눈에 띄었다. 사실 처음 이 문제를 고민하게 된건 프로젝트에 MongoDB 를 사용하게 될것같은데 테스트를 어떻게 진행햐야되는지 무지했기 때문이다. RDB 같은 경우 인메모리 H2 데이터베이스라도 있지만 다른 NoSQL 데이터베이스는 그렇지 않다.

강의에서는 스프링을 처음 시작할때 가장 쉽게 접하는 controller, service, repository 를 이용한 layered architecture 에 한계를 설명하고 전반적으로 Dependency Injection, Dependecncy Inversion Principle, Port-adapter Pattern (또는 Hexagonal architecture) 개념들을 통한 개선 방법과 테스트 코드 작성법을 설명한다.

Port-adapter Pattern

Port-adapter 패턴은 아마 처음듣거나 테스트 코드를 작성을 많이 해보지 않았다면 쉽게 와닿지 않을것 같다.

핵심은 설명해보자면 여기서 port 는 자바 인터페이스의 개념이고 adapter 는 구현체의 개념이다. 간략히 설명하자면 스프링 애플리케이션을 구성하는 여러 컴포넌트 (컨트롤러, 서비스, 리포지토리) 의 의존관계에 인터페이스를 넣어서 의존성을 인터페이스로 위임하고 여러 유즈케이스를 위한 구현체를 만들어서 아키텍처를 구성하는 방법이다. 이렇게 되면 구현체가 바뀌어도 각 구현체는 약속된 인터페이스의 로직을 동일하게 구현하기 때문에 애플리케이션의 확장성이 좋아진다.

alt text

다시 돌아가 port-adpater 이름을 떠올려보면 이제 이름이 좀 더 와닿지 않을까 싶다. 예를 들어 미국에서는 110 볼트 전기 플러그를 쓰고 그 규격에 맞는 어댑터만 꽂을 수 있다.

이제 이걸 테스트 측면에서 바라보면 어떤 컴포넌트에 대해 테스트코드를 작성할때 더 이상 특정 구현체에 의존하지 않아도 되고 더 나아가 테스트 전용 구현체를 만들어서 테스트 코드를 더 쉽고 효과있게 작성이 가능하다.

실무경험과 와닿았던 부분

이때까지의 실무경험과 맞대어 몇가지 와닿았던 부분은 크게

  1. 테스트 코드 작성의 어려움과 그 이유 그리고 해결법
  2. 잘못 작성된 테스트 코드의 문제점

등이 있다.

첫번째 테스트 코드 작성의 어려움

일단 강의애서 여러번 이런 내용이 나온다 “테스트 코드 작성이 어렵다면 전체적인 코드 설계에 문제가 있을 경우가 크다”.

강사는 테스트는 단순히 코드 로직을 넘어서 효과적으로 사용시 코드 설계와 애플리케이션 아키텍처를 개선 할 수 있는 지표/도구가 된다라고 강조한다. 그리고 테스트 코드 작성이 난해하다면 그건 테스트가 경고 메세지를 보내는것일수도 있다라는 것.

실무를 하면서 고생을 많이 했던 부분중 하나다. 작성한 코드에 비해 테스트 코드 작성이 너무 어려울때가 많아고 심지어 테스트 코드를 작성하는 시간의 비율이 훨씬 더 높을 때가 많았다. 이렇게 되면 개발이 재미없어질 수도 있고 개발 속도도 저하된다. 이건 사소해보이지만 반복되었을때 피로도 상당히 쌓이고 점점 개발자의 생산성을 낮춘다.

잘못 작성된 테스트 코드의 문제점

일단 여기서 잘못 되었다는건 테스트 자체가 아니다. 일단 테스트를 작성한다는것 자체만으로도 좋은 방향으로 나아가는 것이라고 생각한다. 다만 무분별하게 요령없이 테스트를 작성하는것은 크게 도움이 되지 않을 수 있다는 점이다.

잘못 작성된 테스트의 몇가지 예시는 다음과 같다:

  1. 의미 없는 테스트. 예를 들어 JpaRepository 구현체로 단순히 인메모리 H2 데이터베이스에 엔티티를 쓰거나 읽는 작업은 크게 의미가 없을 수 있다. 이 부분은 스프링 데이터 JPA 라이브러리의 구현 영역이므로 테스트를 한다고 해도 라이브러리 자체에서 하는게 더 알맞을 것이다.
  2. mock 을 남용하는 테스트. 단순히 mock 을 사용해서 테스트를 작성하는건 크게 효과적이지 않다. mock 은 말그대로 특정부분을 치환하게 되는데 이런식으로 여러 부분을 대역객체로 만들게 되면 로직을 검증한다기 보다는 코드 로직안에서 사용하는 의존성을 제대로 호출하는지만 검증하게 된다.
  3. 무분별한 integration test 의 비율. 앞서 언급했듯이 인메모리 H2 데이터베이스를 이용한 테스트는 실해시 별도로 H2 데이터베이스를 실행까지 해야하기 때문에 추가적인 오버헤드가 발생한다. 이런식의 테스트를 과도하게 작성하게 되면 전체적으로 CI 가 느려지게되고 잘못 테스트를 작성했다가는 불안정한 (flaky) 한 테스트 만들어진다. 이렇게 되면 아까와 비슷한 맥락으로 전체적인 개발 프로세스가 느려진다.

부끄럽지만 실제로 이전에 일하던 곳에서 위에서 언급한 3개의 예시를 모두 겪은것 같다. 애플리케이션의 안정성을 올리기 위해 코드 추가/변경시 해당 코드에 대한 테스트또한 포함을 시키도록 merge 를 block 하는 컨디션을 CI 에 걸어놨는데 아이러니하게도 이 제한은 간혹 의미없는 테스트를 추가하게 하는 원인이 되었던 것 같다.

또한 나는 mock 을 남용하는 의미없는 테스트를 항상 작성해왔던 것 같다. 처음 테스트 코드를 입문을 할때 코드베이스에서 그런류의 테스트를 많이 봐와서 자연스럽게 테스트코드는 이런식으로 작성한다고 인지를 해버렸던 것 같다.

마지막으로 이런 안좋은 practice 가 쌓여서 결국 PR 을 검증하는 CI 는 이미 느려질때로 느려져있었고 심지어 몇몇 테스트는 불안정해서 CI 가 실패라도 하면 다시 긴 시간을 기다려 CI를 재동작해야 했다.

마무리

이전 경험을 회상하며 의미있는 배움을 정리한것 같아서 감사하지만 한편으로는 아쉬움도 많이 남는다. 그때는 이런 문제가 우리회사에 특수할것이라고 자연스럽게 생각했던 것 같고 개인적으로 찾아보거나 동료들하고 깊이 있게 고민해보지 못했다. 앞으로는 맞닥뜨리게 될 개선점들에 대해 도전 할 수 있는 역량과 태도를 가질 수 있었으면 좋겠다.

This post is licensed under CC BY 4.0 by the author.