인프런 워밍업 클럽 스터디 3기 - 백엔드 클린 코드, 테스트 코드 4주차 발자국
Readable Code: 읽기 좋은 코드를 작성하는 사고법Practical Testing: 실용적인 테스트 가이드강의와 함께한 인프런 워밍업 클럽 스터디 3기 - 백엔드 클린 코드, 테스트 코드 (Java, Spring Boot)4주차 발자국 입니다.학습 내용 요약이번 주에는 Practical Testing: 실용적인 테스트 가이드 강의에서 계층형 아키텍처(Layered Architecture) 의 개념을 다시 한번 정리하고, 스프링 기반 테스트에서 자주 사용하는 Mock/Spy 기법과 관련 애너테이션들을 배웠습니다.Layered Architecture (계층형 아키텍처)Presentation Layer: 사용자 입력과 응답을 담당. API 요청/응답 혹은 프론트엔드 인터페이스를 제공하며, 기본적인 파라미터 검증과 변환만 수행.Business Logic Layer: 애플리케이션의 핵심 비즈니스 로직과 규칙을 구현. 서비스(Service)나 도메인(Domain) 로직이 위치하며, 트랜잭션 관리의 주된 대상이 됨.Persistence Layer: 데이터베이스/파일시스템 등 영속성 저장소와 직접적으로 상호작용. Repository(DAO) 형태로 CRUD를 담당.Mockito & Spring Test 관련 애너테이션@Mock / @Spy / @InjectMocks: 순수 Mockito 환경(스프링 컨텍스트 없이)에서 가짜 객체(또는 부분 가짜 객체)를 만들어 단위 테스트를 쉽게 작성할 수 있게 함.@MockBean / @SpyBean: 스프링 애플리케이션 컨텍스트를 기동하는 통합 테스트 환경에서 특정 Bean을 Mock 또는 Spy로 교체하여, 외부 의존성을 간단히 제어하고자 할 때 활용.테스트 설계 방향각 레이어별로 테스트 범위를 명확히 하여, “Persistence → Business → Presentation” 순으로 책임을 분리해 점진적으로 테스트 커버리지를 넓힘.@MockBean / @SpyBean을 과도하게 사용하지 않도록 주의: 꼭 필요한 부분만 모의(Mock/Spy) 처리하고, 나머지는 실제 로직을 이용해 통합 테스트를 안정적으로 수행.테스트 시나리오별로 @BeforeEach를 활용하는 방법과, 각 테스트 메서드에서 독립적으로 given-when-then을 구성하는 방법을 비교.학습 회고얻은 인사이트레이어별 책임과 역할이 분명해야 복잡한 서비스에서도 테스트 설계가 한결 명확해짐을 느꼈습니다.Mock/Spy를 올바른 상황에서만 사용하면 외부 의존성을 최소화하여 빠른 단위 테스트를 작성할 수 있으나, 반대로 불필요하게 남발하면 테스트 유지보수가 어려워짐을 체감했습니다.어려웠던 점@MockBean과 @SpyBean을 사용해 스프링 컨텍스트를 띄울 때 테스트 속도가 느려지거나, 동일 타입 Bean이 여러 개일 때 어느 Bean을 대체하는지 혼동될 수 있었습니다.레이어별 테스트를 작성하면서, 중복으로 보이는 코드(예: 테스트 준비 로직)를 어느 정도까지 @BeforeEach로 추출해야 할지 고민이 되었습니다.미션🎯 Day16 미션: Layered Architecture 구조의 레이어별 특징과 테스트 방법요구사항Layered Architecture에서 각 레이어가 하는 역할, 특징, 그리고 테스트 방식을 자기만의 언어로 정리하기나만의 정리Presentation Layer역할: 사용자나 외부 시스템의 요청을 받고 응답을 전달. 파라미터 검증, DTO 변환, HTTP 상태 코드 결정 등을 담당.특징: 비즈니스 로직을 직접 수행하지 않고, 유효성 체크 후 Service 호출 → 결과 반환에 집중.테스트 방법: 컨트롤러 통합 테스트(@SpringBootTest, @WebMvcTest + MockMvc)를 통해 실제 요청/응답 형식을 모의하거나, 프론트엔드와의 e2e 테스트를 진행해볼 수도 있음.Business Logic Layer역할: 애플리케이션의 핵심 규칙, 알고리즘, 트랜잭션 등의 로직 담당.특징: 여러 Repository를 조합하여 도메인 로직을 수행하고, 예외 처리, Validation 등의 업무 로직을 포괄적으로 관리.테스트 방법: 단위 테스트를 통해 특정 비즈니스 로직의 정확성을 검증. 필요 시 Repository를 Mock 처리(예: @Mock, @MockBean)하여 DB 의존성을 제거하고 로직만 집중 테스트.Persistence Layer역할: 데이터의 저장/조회/수정/삭제 등 영속성 처리에 집중.특징: 쿼리 작성, DB 연결, CRUD 로직을 추상화한 Repository/DAO 형태로 제공.테스트 방법: 실제 DB 혹은 In-Memory DB(H2 등)를 활용한 통합 테스트(@DataJpaTest). 쿼리 정확도, 트랜잭션 처리, 성능 등을 검증하기에 좋음.🎯 Day18 미션 1: @Mock, @MockBean, @Spy, @SpyBean, @InjectMocks의 차이점 정리요구사항각 애너테이션이 어떤 환경(단위 테스트/통합 테스트)에서 사용되고, 어떤 특징이 있는지 정리하기1) @Mock주로 사용 환경: Mockito (단위 테스트)스프링 빈 등록 여부: X (별도 Bean 아님)특징:순수 Mock 객체를 생성하여, 내부 로직 없이 호출 기록/결과(Stubbing)만 지정할 수 있음단위 테스트에서 외부 의존성을 배제하고 특정 로직만 검증할 때 유용2) @MockBean주로 사용 환경: Spring Boot Test(통합 테스트)스프링 빈 등록 여부: O (빈으로 등록됨)특징:스프링 애플리케이션 컨텍스트에 등록된 실제 Bean을 Mock으로 교체다른 Bean이 해당 Bean을 의존하고 있으면, 그 의존성도 가짜(Mock)로 처리됨@SpringBootTest나 @WebMvcTest 등 스프링 컨텍스트가 뜨는 환경에서 사용3) @Spy주로 사용 환경: Mockito (단위 테스트)스프링 빈 등록 여부: X (별도 Bean 아님)특징:부분 가짜(Spy) 객체를 생성기본적으로 실제 메서드 동작을 유지하면서, 필요한 부분만 가짜(Stubbing)로 설정 가능복잡한 객체를 테스트할 때, 일부는 실제 로직을 실행하고 일부만 Mock 처리할 수 있어 유연함4) @SpyBean주로 사용 환경: Spring Boot Test(통합 테스트)스프링 빈 등록 여부: O (빈으로 등록됨)특징:스프링 컨텍스트 내 실제 Bean을 Spy로 교체기본적으로는 실제 로직을 수행하되, 특정 메서드만 가짜로 동작시키거나 호출을 기록할 수 있음통합 테스트 환경에서 부분 Mocking이 필요할 때 적합5) @InjectMocks주로 사용 환경: Mockito (단위 테스트)스프링 빈 등록 여부: 해당 없음특징:@Mock 또는 @Spy로 만들어진 객체들을 자동으로 주입(생성자, 세터, 필드 순)해줌예: @InjectMocks UserService라면, UserService가 의존하는 Repository나 다른 객체들을 @Mock으로 생성해 주입받을 수 있음단위 테스트에서 여러 의존 객체를 편리하게 Mock/Spy로 대체할 수 있도록 지원 🎯 Day18 미션 2: 테스트 시나리오(@BeforeEach, given, when, then) 구성요구사항아래 3개의 테스트 메서드가 있을 때, “어떤 준비 로직을 @BeforeEach로 뽑고, 어떤 부분을 각 테스트의 given절에 둘지”, “when절을 어떻게 구성할지”를 구상해보기예시 테스트 3종사용자가 댓글을 작성할 수 있다.사용자가 댓글을 수정할 수 있다.자신이 작성한 댓글이 아니면 수정할 수 없다.나의 구성@BeforeEach void init() { // 공통 데이터를 미리 생성하지 않습니다. /* * 각 테스트가 각각의 상황(사용자, 게시물, 댓글)을 자유롭게 구성할 수 있도록, * 여기서는 사전에 아무것도 세팅해두지 않습니다. * 테스트마다 필요한 데이터 타입이나 조건이 다를 수 있으므로, * 각 테스트 메서드 내부에서 직접 객체를 생성하고 준비해 주는 방식을 채택했습니다. */ } @DisplayName("사용자가 댓글을 작성할 수 있다.") @Test void writeComment() { // given // 1-1. 사용자 생성에 필요한 내용 준비 // 1-2. 사용자 생성 // 1-3. 게시물 생성에 필요한 내용 준비 // 1-4. 게시물 생성 // 1-5. 댓글 생성에 필요한 내용 준비 // when // 1-6. 댓글 생성 // then // 검증 } @DisplayName("사용자가 댓글을 수정할 수 있다.") @Test void updateComment() { // given // 2-1. 사용자 생성에 필요한 내용 준비 // 2-2. 사용자 생성 // 2-3. 게시물 생성에 필요한 내용 준비 // 2-4. 게시물 생성 // 2-5. 댓글 생성에 필요한 내용 준비 // 2-6. 댓글 생성 // when // 2-7. 댓글 수정 // then // 검증 } @DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.") @Test void cannotUpdateCommentWhenUserIsNotWriter() { // given // 3-1. 사용자1 생성에 필요한 내용 준비 // 3-2. 사용자1 생성 // 3-3. 사용자2 생성에 필요한 내용 준비 // 3-4. 사용자2 생성 // 3-5. 사용자1의 게시물 생성에 필요한 내용 준비 // 3-6. 사용자1의 게시물 생성 // 3-7. 사용자1의 댓글 생성에 필요한 내용 준비 // 3-8. 사용자1의 댓글 생성 // when // 3-9. 사용자2가 사용자1의 댓글 수정 시도 // then // 검증 }@BeforeEach정말 모든 테스트에 공통으로 필요한 세팅(예: DB clean-up, 동일한 테스트 데이터 세팅 등)이면 이곳에 배치.하지만 테스트마다 시나리오가 크게 다른 경우, 오히려 각 테스트 내부(given 절)에서 데이터를 생성하는 편이 가독성과 유연성이 좋아질 수 있음.given 절테스트에 필요한 사전 조건(사용자, 게시물, 댓글 등) 및 Mock/Stubbing이 필요하다면, 해당 로직을 배치.시나리오별로 조금씩 다른 상황을 구성할 때, @BeforeEach보다 각 테스트 내부의 given 부분에 명시적으로 작성하는 방식이 선호됨.when 절실제 테스트 대상 메서드를 호출하는 구간. 한 개의 테스트에서 “정확히 하나의 when 절”을 유지해, 명확히 어떤 동작을 검증하는지 드러내도록 함.then 절결과 검증(Assertions) 수행. 저장된 데이터 확인, 예외 발생 여부 확인, 반환값 확인 등.미션 회고레이어별 특징을 자기 언어로 설명단순히 문서상 개념을 반복하기보다, 실제 현업/프로젝트에서 마주치는 구조를 떠올리며 다시 풀어내보니, 각 계층이 왜 필요한지, 테스트를 어떻게 접근해야 하는지 더 명확해졌습니다.Mock/Spy 애너테이션 정리정리를 해보니 실제로 코드에 적용할 때 어떤 상황에서 무엇을 써야 하는지가 더 분명해졌습니다.@MockBean/@SpyBean이 스프링 컨텍스트를 사용하는 통합 테스트에서 유용하지만, 속도와 Bean 교체 이슈가 있어 주의가 필요하다는 점이 인상적이었습니다.3가지 테스트 시나리오에서 @BeforeEach & given/when/then 배치시나리오에 따라 @BeforeEach를 최소화하거나, 여러 테스트에 공통적으로 필요한 부분만 추출하는 방식이 각각 장단점이 있음을 확인했습니다.테스트 구조를 “given-when-then”으로 고정하면 가독성이 올라가지만, 너무 세세한 단계 분리는 오히려 장황해질 수 있으므로 균형을 잡아야겠습니다.회고칭찬하고 싶은 점레이어별 테스트 기법과 Mock/Spy 전략이 한층 체계적으로 잡혔습니다.실제 코드를 작성하며, “가짜 객체를 어디까지 써야 하나? 외부 연동은 어떻게 테스트하나?” 같은 고민을 많이 해 봐서, 앞으로는 목적에 맞는 테스트를 설계하는 능력이 향상될 것 같습니다.아쉬웠던 점 & 보완 계획Mock 남발 시 발생할 수 있는 문제(설정 복잡도 상승, 실제 로직 변경 시 Stubbing 깨짐)를 더 구체적인 예제로 다뤄보면 좋을 것 같습니다.@BeforeEach와 개별 테스트 내부 given 절의 적절한 균형점을 찾으려면, 실제 현업 수준의 다양한 테스트 케이스를 더 경험해 봐야겠습니다.다음 목표예외 상황 테스트 강화단순 성공 케이스뿐 아니라, 비정상 입력이나 예외 케이스를 좀 더 체계적으로 정리하고 테스트에 반영. 도메인 별로 테스트 슬라이싱@DataJpaTest, @WebMvcTest 등 스프링이 제공하는 슬라이스 테스트 방식을 적극 활용해 보기.테스트 실행 속도 최적화통합 테스트와 단위 테스트를 적절히 조합하여, 빠르면서도 신뢰성 있는 테스트 환경을 구축해 보기.이번 4주차에는 계층형 아키텍처를 다시 한번 복습하면서 Mock/Spy, @MockBean/@SpyBean 등 스프링 테스트 환경에서 자주 쓰이는 기법들을 정리하고 적용해 보았습니다. 학습 내용과 미션을 통해 각 레이어가 가진 의미와 책임이 더욱 또렷해졌고, 가짜 객체를 어떻게 잘 활용해야 하는지 감이 잡힌 것 같습니다.앞으로도 학습 내용을 기록하고 회고하면서, 점점 더 탄탄한 테스트 코드를 작성해 가도록 하겠습니다.감사합니다!