
의존성 주입을 통해 쉬운 테스트를 만드는 방법
우선 강의는 Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트 이며, 해당 강의를 통해 학습한 내용을 정리해보았다.
강의 후기는 그동안 추상적인 개념으로만 알고 있던 내용을 정말 잘 정리할 수 있는 기회이자 진작에 들었으면 많은 시행착오를 겪지 않아도 될만한 내용들이 강의에 담겼다고 본다. 그리고 개인적으로 이 강의가 가장 훌륭한점은 전달력이다. 해당 강사분은 카카오 개발자시니 개발 실력은 말할것도 없고, 딕션이나 설명하고자 하는 내용들이 부담없이 머리에 잘 들어온다. 개인적으로는 내용도 좋지만 전달력이 가장 좋아서 6시간 20분의 강의를 겨우 2~3일만에 전부 들었던 것 같다.
테스트의 중요성
해당 강의에서는 기존 레이어드 아키텍처에서 출발하여 클린 아키텍처의 구현인 헥사고날까지 코드를 변경하며 테스트의 중요성에 대해 얘기한다. 개인적인 생각이지만 테스트의 큰 장점 중 하나는 리펙토링이라고 생각하는데 해당 강의에서는 아키텍처를 변경하며 일어나는 무수히 많은 리펙토링을 안정적으로 수행하는 과정을 통해 테스트의 장점을 볼 수 있었다.
강의에서 언급하듯 아주 간단한 프로젝트 (단순 CRUD)만 있는 경우 테스트가 없어도 상관없지만 조금만 복잡해져도 테스트가 필요하다고 말한다.

하지만 너무 테스트에만 매몰되어도 안된다. 강의 자료 중 클린 아키텍처의 추상화는 어디까지 되어야 하는지에 대해 “정답은 없다.”고 그냥 적당한 지점을 찾으면 된다고 한다.
의존성 주입이란?
단순하게 보자면 객체가 다른 의존성을 갖는것을 막고 의존성을 약화시키기 위해서 객체 내부에서 new 생성자를 통해 다른 객체를 생성하지 않고, 외부에서 파라미터로 전달받는 방법이다.

그 이유는 new는 사실상 하드코딩이며, 해당 클래스가 변경이 되는 시점에 의존성이 있는 모든 클래스를 변경 해야하고, 이는 OCP에 위배되기도 한다.
그렇게 의존성을 주입받고.. 또 Chef를 호출하는 객체에서 Bread, Meat, Lettuce 등을 직접 생성하지 않고 의존성을 주입받아서 전달하고, 또 반복하다보면 결국 특정 클래스에서는 의존성을 주입하기 위해 하드 코딩인 new를 통해 객체를 생성하게 된다.
Spring 프레임워크는 의존성 주입을 통해 개발자가 특정 클래스에서 직접 의존성을 관리하는 대신, 특정 규칙에 따라 Spring이 제어권을 가지고 의존성을 주입할 수 있도록 한다. 이러한 접근 방식은 코드의 유연성과 유지보수성을 향상시켜 특정 클래스에서 하드 코딩을 하지 않도록 도와준다.
이게 왜 중요하냐면 클래스를 테스트하는 경우 내부에서 직접 객체를 생성하게 된다면 테스트하기 어려워진다는 문제가 발생하기 때문이다.
예를 들어서 랜덤한 값을 생성하는 클래스가 내가 테스트하려는 클래스 내부에서 사용하고 있고, 그것에 대한 값을 맞추려면 이는 리플렉션이나 다이나믹 프록시 같은 별도의 복잡한 패턴을 구현하는 방식이 아니라면 랜덤한 값을 맞출 수 없게되고 이는 테스트 코드를 작성하는데 아주 큰 벽이 된다.

Repository와 JPA의 의존성을 끊자
그럼 이제 본론으로 돌아가서 xxxService를 테스트할 때 가장 문제가 되는 부분은 JPA에 강하게 결합되어 테스트하기 어려움에 있습니다. 이를 해결하기 위해 `@SpringBootTest`를 사용하는 방법이 있지만 스프링 컨테이너를 띄우고 실제 DB(혹은 테스트용 H2)를 사용해야 하는데 이는 무겁고 오래 걸리는 등 FIRST (좋은 테스트 조건)를 위배한다고 볼 수 있다.
무작정 통합 테스트가 나빠! 는 아니지만, 핵심 비즈니스 로직을 간단하게 테스트하고 회귀버그를 방지하면서 리펙토링 하기 쉽도록 하려면 단위 테스트(스몰 테스트)는 필수적이며 가장 많은 부분을 차지해야 한다.
이를 위해서 Repository를 JPA에 의존하는 방법은 지양해야 한다고 볼 수 있다.

결국 위 이미지에서 처럼 Serivice에서 직접 JPARepository를 의존하지 않고, 필수적인 API만 명시되어 있는 Repository 인터페이스를 생성하여 이를 의존하도록 만드는 방법이다.
그 다음 Repository의 구현체에서 JPARepository를 의존하도록 만들게 된다면 Coupling은 낮아지고 SOLID 원칙을 지키는 좋은 아키텍처가 만들어진다.
이제 이렇게 하게된다면 테스트 코드를 작성할 때 ModelRepository 인터페이스를 상속한 FakeModelRepository를 만들어서 테스트에 사용하게 된다면 위에서 언급한 DB가 없이도 테스트가 가능한 코드가 만들어진다. 외부 의존성이 아예 없기 때문에 빠르고, 계속해서 테스트를 돌리면서 코드를 수정할 수 있기 때문에 배포에 대한 두려움도 사라지게 되는 장점도 있다.
Controller랑 Repository는 테스트 안하나요?

해당 강의에서는 프레임워크와 라이브러리를 믿고 간다고 한다. 물론 이게 정확하지 않을 수 있지만 나의 생각에도 개발자 개인이 하는 것 보다, 해당 조직에서 더 정확하게 테스트할거라고 생각한다. 그렇기 때문에 우리가 만든 코드인 서비스와 도메인 정도까지만 테스트하는것이 맞다고 본다. (현재 프로젝트에서는 DTO까지 테스트 했다.)

결론(어디까지 의존성 주입을 해야할까)

이 방법의 가장 큰 장점은 JPA Entity와 Domain Entity를 나누었을 때 발휘한다고 강의에서 설명하지만 현재 Domain이 아주 작은 상태이기 때문에 현재 상태를 유지하면서 추후 서비스가 커진다면 Controller에서 Service를 직접 의존하지 않도록 변경하고, 순환 참조를 막기 위해서 DTO 객체를 계층 별 생성하면서 Entity를 JPA와 Domain으로 별도로 나누게 된다면 헥사고날 패턴으로 구현이 가능하겠지만 이전 단계에서도 꽤나 훌륭한 아키텍처라고 생각된다.
강의와 예시 코드를 통해 기존에 생각도 못해봤던 테스트 범위나 효과에 대해서 알게 되었던 것 같습니다. 강의는 길었으며 깨달은 내용도 많았지만 가장 중요한건 딱 두가지라고 생각한다.
1.테스트가 어렵다면 코드를 변경해야하는 신호이며, 의존성 역전과 아키텍처 변경을 통해 해결이 가능하다.
2.무작정 오버 엔지니어링을 하지말고, 서비스의 규모와 개발자 리소스에 적절하게 아키텍처를 설계하자.
만약 프로젝트가 복잡해져서 아키텍처 변경이 필요하다고 생각되는 경우, 각 계층을 분리하고 의존성을 단방향으로 만들기 위해 현재 Entity를 Domain Entity와 DB Entity로 변경하면서 그외에 CQRS 패턴을 적용하고, ~~Service 보다 ~~Reader, ~~Writer로 인터페이스를 나누어 구현하는 방향으로 간다면 커진 서비스를 테스트하고 운영하는데 많은 도움이 될 것으로 보인다.

강의 링크 https://inf.run/5VcPj
Java/Spring 테스트를 추가하고 싶은 개발자들의 오답노트| 김우근 - 인프런 강의
현재 평점 4.9점 수강생 2155명인 강의를 만나보세요. Spring에 테스트를 넣는 방법을 알려드립니다! 더 나아가 자연스러운 테스트를 할 수 있게 스프링 설계를 변경하는 방법을 배웁니다. Spring에
www.inflearn.com
'Languege > Java & Spring' 카테고리의 다른 글
| [Spring] Java Validation은 어떻게 한글 메시지가 나올까? 배포 중 장애 발생 (1) | 2025.07.17 |
|---|---|
| [Swagger] Spring, Spring Boot 호환되는 버전 정리 (1) | 2025.07.08 |
| [Spring Transaction] TransactionManager 동작방식 (0) | 2025.01.24 |
| [Java] jdk 17 - toList의 함정 (0) | 2024.11.14 |