회사 테스트 코드 도입기

2023. 4. 27. 00:06개발공부/테스트 주도 개발

회사에 테스트 코드가 있지만, 프로젝트를 진행하면서 작성하지 않고 있어 새로운 프로젝트를 들어가며 이번엔 테스트 코드를 작성하기로 팀원과 결정했습니다.

도입에 앞서 고민했던 점들과 어떻게 도입했는지를 한번 정리해봤습니다.

테스트 코드를 적용해야 하는 이유

  • 코드에 신뢰성이 생긴다.
  • 리팩토링이 쉬워진다.

테스트 코드를 적용하기 위한 준비

테스트 단위

  • 테스트를 어떤 범위까지 진행할지 선택. 예를 들어 단위 테스트, 통합 테스트, end-to-end(e2e) 테스트

테스트 방법론

  • 의존성이 걸린 클래스일 때 mock을 적극적으로 사용할지, 실제 인스턴스를 활용할지

선택한 방법

  • mock은 비즈니스 로직 검증이 어려워, 회귀 방지가 낮다고 생각했습니다. 따라서 mock보다 실질적인 인스턴스 테스트를 진행하기로 했습니다.
  • 그 과정에서 필요한 테스트에 리소스를 쏟고자 mock이 많이 쓰일 graphql Resolver 클래스 e2e(앤드투앤드) 테스트는 생략하기로 했습니다. 대신 DB 테스트와 단위 테스트로 보완하고자 했습니다.
  • 간단한 예시로, verify로 메서드가 호출했는지 검증하기보다 검증대상 상태의 변화와 결과값을 assert로 검증하는 것을 위주로 선택했습니다.

1. DB Test

mongoDB

DB 테스트는 비즈니스 규칙을 검사할 필요가 없습니다. Service는 Repository 값을 Mock이나 Stub으로 주고,
쿼리 테스트는 Repository에서 해야합니다. 따라서 DB를 비즈니스 규칙과 분리해야합니다. 테스트 DB를 만들어서 쿼리 함수를 하나씩 호출한 후, DB에 원하는 효과가 일어나는지 확인하면 됩니다.

비즈니스 규칙을 테스트할 때는 스텁이나 스파이로 Repository 인터페이스를 대체하고, 진짜 DB에 연결한 채로 비즈니스 규칙을 테스트해선 안됩니다. 진짜 DB는 느리고 오류가 더 잦습니다. 대신 비즈니스 규칙과 Service가 Repository를 정확하게 조작하는지만 테스트해야 합니다.

In-memory DB

flapdoodle-embed-mongo를 사용해 Spring Data Mongo JPA 쿼리까지 테스트 할 수 있습니다. 하지만 단위 테스트 책에선 In-memory DB를 피하라고 돼있습니다. In-memory DB를 사용해도 괜찮을까요?

테스트 DB 인스턴스가 따로 관리된다면 베스트 케이스입니다. 하지만 스크립트를 만들어 테스트 할 데이터를 심어놓고 코드로 관리하는 리소스가 많이 드는 것이 현실입니다. 추가 서버도 필요하기 때문에 시작부터 리소스를 크게 잡기엔 현 상황에서 좋은 안은 아니라고 생각했습니다. 대안으로 마이그레이션 기반 방식을 사용한다면 툴(ex. mongobee)을 붙여야 하므로 그대로 리소스가 듭니다.

책에서 In-memory를 피하라는 이유로는 공유 의존성이 아니기 때문에 통합 테스트는 컨테이너 접근 방식과 유사한 단위 테스트가 된다고 합니다. 또한 일반 DB과 기능적으로 일관성이 없기 때문에 사용하지 않는 것이 좋다라고 합니다. 이는 또 다시 운영 환경과 테스트 환경이 일치하지 않는 문제이며, 일반 DB와 In-memory DB의 차이로 인해 테스트 회귀가 떨어진다고 했습니다.

기능적으로 일관성이 없다라는 건 RDBMS 벤더마다 SQL 문법이 약간씩 다르기 때문에 얘기하는 것으로 생각했습니다.(ex. h2, SQLite ↔ MySQL, Oracle) mongo는 벤더가 나뉘지 않고, flapdoodle 방식이 mongodb를 다운로드 받아서 캐시에 저장한다음 In-memory 방식으로 동작하기 때문에 기능적으로 일관성이 있다고 생각한다. 만약 있다고 해도 mongo 버전별 차이가 있을 뿐 회귀 방지가 떨어져 거짓 양성, 음성을 야기시킬 정도의 차이는 아니라고 판단했습니다.

그래서 저희는 DB 테스트는 flapdoodle-embed-mongo를 사용하기로 결정했고, 현재까지 복잡한 쿼리까지도 잘 테스트하고 있습니다.

image

2. Service Test

Repository 통해 DB 데이터를 가져오는 메서드는 DB Test가 진행하기 때문에 비즈니스 로직만을 검증합니다.

Fake Object

  • 비즈니스 로직을 검증할 때, 인스턴스가 필요한 경우 Fake Object를 사용해 mock 사용을 줄였습니다. 다만 서비스 테스트에선 DB와의 의존성 분리를 위해 Repository 클래스는 Mock으로 가짜 객체를 주입함
  • 또한 도메임 클래스에 위임할 수 있는 로직은 적극적으로 위임해 서비스 클래스 위존성을 줄인다. 이렇게 되면 테스트 코드를 만들기 쉬운 코드가 되고 도메인 클래스가 담당하는 역할을 명확히 위임할 수 있다.

3. 그 외

Service 클래스의 private 메서드는 테스트 해야할지에 대한 의문이 생겼습니다. 향로님의 블로그 글을 참고하고, 이전 넥스트 스텝에서 주관한 TDD 강의에서도 그랬듯 해결책은 도메인에 위임할 수 있다면 위임하는 것이었습니다.


참고자료