블로그
전체 32025. 03. 23.
0
워밍업 클럽 3기 BE 클린코드&테스트 - 3주차 발자국
“작동하는 코드는 끝이 아니라 시작이다. 진짜 개발자는 ‘테스트 가능한 설계’에서부터 출발한다.”1단계: Persistence Layer 테스트 – 단위의 본질을 의심하라JPA 테스트는 대부분 @DataJpaTest로 간단히 시작된다. 하지만 내가 여기에 의문을 품기 시작한 건 다음과 같은 경험 때문이다엔티티 간의 복잡한 연관관계 (특히 LAZY 로딩) 때문에 테스트 시점에서 LazyInitializationException이 자주 터졌다.단순한 CRUD 테스트는 통과했는데, 실제 서비스에서는 N+1 문제나 flush 타이밍 이슈가 터졌다.그래서 Persistence 테스트에서 다음과 같은 원칙을 세웠다:단위 테스트로 만족하지 마라. 연관관계와 Fetch 전략까지 검증하라.실제 @Transactional 환경에서의 쿼리 생성을 직접 로그로 확인하라.@DataJpaTest보단 통합 테스트에 가까운 Repository 테스트를 설계하라.→ 이 경험은 설계할 때부터 “테스트 가능한 엔티티 구조인가?”를 고민하게 만들어줬다.2단계: Business Layer 테스트 – Mock보다 책임을 분리하라Service 레이어 테스트에서는 보통 @MockBean을 통해 Repository를 가짜로 주입하고 시작한다. 하지만 실제 테스트를 하면서 난 이렇게 깨달았다:Mock이 많아질수록 테스트는 ‘신뢰’가 아니라 ‘세팅’으로 변질된다.비즈니스 로직이 애매하게 도메인 모델과 얽혀 있으면 테스트가 지나치게 복잡해진다.그래서 Service 레이어 테스트에서 내가 택한 전략은 다음과 같다.의존성 주입보다 ‘책임 분리’에 집중하자.유틸성 도메인 서비스는 과감하게 별도 클래스로 분리했다.트랜잭션이 필요한 핵심 로직만 핵심 Service에 남겼다.Mock은 ‘부작용 있는 의존성’에만 최소한으로 사용한다.외부 API 호출, 파일 처리 등 순수하지 않은 작업에만 Mock 사용.나머지는 가능하면 In-Memory DB를 통한 통합 테스트로 전환.→ 이 과정에서 테스트 설계 자체가 코드 구조 개선으로 이어졌다. 테스트는 곧 설계의 거울이다.3단계: Presentation Layer 테스트 – Controller 테스트는 시나리오다Controller 테스트는 예전엔 단순히 @WebMvcTest로 시작했지만, 실제 프로젝트에서 다양한 인가/인증 이슈를 겪으면서 이렇게 정리하게 되었다.Controller 테스트는 단위 테스트가 아니라 시나리오 테스트다.사용자의 행위 흐름을 기준으로 경로를 검증하라.예외 케이스(잘못된 입력, 인증 오류 등) 중심으로 설계하라.MockMvc로 충분한가? 아니면 통합 테스트로 전환해야 하는가?Security 적용 후 로그인 기반 테스트를 위해 @SpringBootTest + TestRestTemplate을 사용.인증 흐름도 테스트 시나리오 안에 포함되도록 구성.DTO 검증은 Bean Validation으로 끝내지 마라.검증 오류 발생 시 API 응답이 일관되게 포맷되는지, 실제 클라이언트 입장에서 설계하라.→ 사용자의 흐름을 기반으로 테스트를 설계하면서, 오히려 API 설계가 더 명확해졌고 팀원들과의 커뮤니케이션도 쉬워졌다. 4단계: Mock을 줄이고 실제 DB를 활용한 테스트를 할 때, 성능과 신뢰성 사이의 균형은 어떻게 잡아야 할까?실제 DB를 활용한 테스트(In-Memory 또는 TestContainer 기반 통합 테스트)는 신뢰성 측면에서 분명 강점이 있다. 하지만 테스트 속도가 느려지고, 유지보수도 까다로워진다는 단점이 따른다. 이 균형은 "테스트 목적에 따른 전략적 분리"로 해결할 수 있다.✅ 1. 테스트 목적에 따라 레벨을 명확히 나누기테스트 레벨목표특징Unit Test로직 단위 검증빠름, Mock 적극 사용Slice TestSpring Context 일부 + 실제 Bean@DataJpaTest, @WebMvcTest 등 사용Integration Test실제 환경과 유사한 흐름 검증느림, 신뢰도 높음→ 단위 테스트(Unit)는 빠르게, 통합 테스트(Integration)는 신뢰 높게하자. 각각의 역할을 명확히 구분하는 게 핵심이다. ✅ 2. 테스트 커버리지를 ‘리스크 중심’으로 관리하기전체 커버리지를 100%로 맞추는 건 비효율적이다. 나는 아래 기준으로 실제 DB를 쓰는 테스트를 판단하는게 좋다고 생각한다.DB 연산이 중요한 의미를 가지는 도메인→ ex) 복잡한 조건의 쿼리, QueryDSL 동적 쿼리, JPA 연관관계 테스트트랜잭션과 영속성 컨텍스트 동작에 따라 결과가 달라지는 로직→ ex) dirty checking, flush, cascade 등API 시나리오 상 치명적인 실패가 발생할 수 있는 핵심 흐름→ ex) 주문 생성, 결제 처리 등위 영역은 느려도 통합 테스트로 보장하고, 나머지는 단위 테스트로 빠르게 검증한다.✅ 내 결론: “가짜로 빠르게 vs. 진짜로 느리게”가 아니라, “목적 중심으로 정교하게”단순히 속도만을 쫓아 Mock으로 도배된 테스트는 어느 순간부터 ‘돌아는 가는데, 신뢰할 수 없는 테스트’가 된다. 반대로 모든 테스트를 실제 DB로 돌리는 건 느리고 관리가 어렵다.그래서 나는 이렇게 정리했다:"테스트의 목적은 속도도, 커버리지도 아닌 ‘신뢰’다. 그리고 신뢰는 목적에 따라 얻을 수 있다."마무리하며 – 테스트는 개발자의 언어다나는 테스트를 단지 “코드가 잘 돌아가는지 확인하는 수단”으로 보지 않는다. 테스트는 설계를 검증하는 도구이자, 개발자가 소통하는 언어라고 생각한다. 이 기준을 가지고 나면 단순히 "커버리지 높이기"가 아니라 "의도 명확하게 만들기"에 집중하게 된다.앞으로도 나는 테스트를 통해 설계하고, 설계를 통해 테스트를 더 단단히 할 것이다.
백엔드
・
Test
・
Spring
2025. 03. 16.
0
워밍업 클럽 3기 BE 클린코드&테스트 - 2주차 발자국
코드 리팩토링을 통한 성장: 주간 회고1. 리팩토링을 시작하게 된 계기이번 주는 코드 리팩토링을 집중적으로 진행했다. 기존 코드에서 개선할 점을 찾아 수정하는 과정에서 단순한 문법 개선을 넘어, 코드의 유지보수성과 가독성을 향상시키는 것이 얼마나 중요한지 다시금 깨닫게 되었다.리팩토링을 진행한 코드(PR): GitHub 링크이 과정을 통해 단순한 기능 구현이 아니라, 보다 효율적인 코드 작성법과 객체 지향적인 사고 방식을 익히게 되었다.2. 리팩토링 과정과 개선 포인트(1) 중복 코드 제거 및 메서드 추출기존 코드에서는 비슷한 로직이 여러 메서드에서 반복적으로 사용되고 있었다. 이를 해결하기 위해 메서드 추출 기법을 적용했다.기존 코드에서는 여러 곳에서 동일한 로직을 복사-붙여넣기 했지만, 공통 로직을 별도의 메서드로 분리하여 재사용성을 높였다.이 과정에서 단일 책임 원칙(SRP, Single Responsibility Principle) 을 더욱 깊이 이해할 수 있었다.(2) 가독성을 높이는 네이밍 개선리팩토링 전에는 변수명과 메서드명이 애매하여 코드의 의도를 명확히 파악하기 어려웠다. 개선 과정에서 다음과 같은 기준을 적용했다.메서드명은 동작을 명확하게 설명할 수 있도록 동사 + 목적어 형식으로 변경변수명은 의미를 명확하게 전달할 수 있도록 명명 (예: temp → formattedDate)코드 리뷰 과정에서 네이밍의 중요성을 다시금 깨달았다. 가독성이 높아지면 코드의 이해도가 높아지고, 유지보수도 쉬워진다.(3) 불필요한 의존성 제거 및 클래스 분리기존 코드에서는 한 클래스가 너무 많은 역할을 담당하고 있었다. 이를 해결하기 위해 책임을 분리하고, 역할에 맞는 클래스를 생성했다.기존의 거대한 클래스에서 역할별로 클래스를 분리하여 객체 지향적인 구조로 개선불필요한 의존성을 제거하고, 의존성 역전 원칙(DIP, Dependency Inversion Principle) 을 적용하여 유연성을 높였다.SRP를 적용한 후, 코드의 변경이 필요할 때 한 곳만 수정하면 되어 유지보수성이 크게 향상되었다.3. 다른 개발자들의 경험에서 배운 점이번 리팩토링을 진행하면서 다른 개발자들이 작성한 후기도 참고했다. (Inflearn 블로그 링크 모음)여러 후기에서 공통적으로 강조하는 몇 가지 핵심 사항을 발견했다.리팩토링의 본질은 단순한 코드 변경이 아니라 유지보수성과 확장성을 높이는 것코드가 동작한다고 끝이 아니라, 더 나은 코드로 개선하는 과정이 필요함.리팩토링은 협업과 코드 리뷰를 통해 더욱 효과적으로 이루어진다혼자 작업할 때는 발견하지 못했던 문제점들이, 코드 리뷰를 통해 드러남.다른 개발자들의 시각에서 개선점을 찾는 것이 중요함.객체 지향 원칙을 적용하는 것이 리팩토링의 핵심이다SOLID 원칙을 고려하며 리팩토링할 때 코드가 더욱 구조적으로 개선됨.특히 단일 책임 원칙(SRP), 의존성 역전 원칙(DIP) 을 적용하면 코드의 확장성이 크게 증가함.이번 리팩토링을 통해 나 또한 이 점을 깊이 체감했다.4. 리팩토링을 통해 얻은 교훈이번 경험을 통해 얻은 가장 큰 교훈은 "리팩토링은 단순한 코드 수정이 아니라, 코드의 가치를 높이는 과정이다." 라는 것이다.코드는 팀원과 미래의 나를 위한 문서와 같다. 가독성이 좋고, 유지보수가 쉬운 코드가 진짜 좋은 코드다.코드 리뷰를 적극적으로 활용하자. 다른 개발자들의 피드백을 통해 더 나은 개발자가 될 수 있다.객체 지향 원칙을 익히고 실천하자. SOLID 원칙을 고려하며 개발하는 것이 장기적으로 가장 효율적인 방법이다.이러한 리팩토링 경험을 반복하면서, 더욱 성장하는 개발자가 되어야겠다는 다짐을 하게 되었다. 앞으로도 주간 단위로 배운 내용을 정리하며 지속적인 성장을 기록할 예정이다.✍ 앞으로의 다짐매주 코드 리팩토링을 진행하고, 개선된 내용을 블로그에 정리하기코드 리뷰 문화를 적극적으로 활용하고, 동료 개발자들과 협업하며 성장하기SOLID 원칙과 디자인 패턴을 공부하고, 실무에서 적용할 수 있도록 연습하기이번 리팩토링 경험을 통해 얻은 교훈을 앞으로도 개발 과정에 적용하며, 더 나은 개발자로 성장해 나가겠다!
백엔드
・
워밍업클럽3기
・
클린코드
2025. 03. 09.
0
워밍업 클럽 3기 BE 클린코드&테스트 - 1주차 발자국
발자국 - 학습 회고강의 수강📌 학습 내용 요약이번 주에는 SOLID 원칙과 Clean Code에 대해 집중적으로 학습했다. 특히, 개념적인 이해뿐만 아니라 실제 코드에 적용하는 연습을 병행하며, 단순히 원칙을 아는 것과 이를 실천하는 것의 차이를 느낄 수 있었다.SRP(단일 책임 원칙): 클래스는 하나의 책임만 가져야 한다.OCP(개방-폐쇄 원칙): 기존 코드를 변경하지 않고 확장할 수 있도록 설계해야 한다.LSP(리스코프 치환 원칙): 자식 클래스는 부모 클래스를 대체할 수 있어야 한다.ISP(인터페이스 분리 원칙): 클라이언트가 필요하지 않은 기능에 의존하지 않도록 인터페이스를 분리해야 한다.DIP(의존성 역전 원칙): 상위 모듈이 하위 모듈에 직접 의존하지 않고 추상화에 의존해야 한다.Clean Code 관련해서는 함수의 길이를 줄이고, 네이밍을 명확하게 하는 것의 중요성을 다시금 체감했다.🔍 학습 회고이번 주는 단순히 원칙을 공부하는 것이 아니라, 실제 코드에 적용하며 문제를 해결하는 과정에서 많은 고민을 했다. 기존에 알고 있던 개념들이 실전에서는 어떻게 변하는지 경험해볼 수 있는 시간이었고, 특히 OCP와 DIP 원칙을 적용하는 과정에서 생각보다 어려움을 겪었다.이전에는 새로운 기능을 추가할 때 기존 코드를 직접 수정하는 방식으로 접근했는데, 이번 학습을 통해 확장성을 고려한 설계를 어떻게 할지 고민하는 습관이 생겼다.🌟 스스로 칭찬하고 싶은 점개념 학습에서 그치지 않고, 직접 적용해보려 노력했다.코드 리뷰를 하면서 다른 사람의 접근법도 참고하며 내 코드에 반영했다.실수했더라도 다시 돌아가 원인을 분석하고 개선하는 태도를 유지했다.🤔 아쉬웠던 점과 보완하고 싶은 점DIP 원칙을 적용할 때, 인터페이스를 활용하는 방법에 대한 이해가 부족했다.디자인 패턴을 함께 학습하면 더 효과적으로 원칙을 적용할 수 있었을 것 같다.코드 리팩토링 과정에서 너무 고민을 많이 하면서 시간을 초과한 경우가 있었다.🎯 다음 주 학습 목표SOLID 원칙을 활용하여 기존 코드 리팩토링을 해보기디자인 패턴(특히 전략 패턴, 팩토리 패턴)을 함께 학습하여 원칙 적용을 더 자연스럽게 만들기리팩토링 시 트레이드오프를 고려하며 현실적인 선택을 하는 연습하기미션🏆 미션 해결 과정이번 주 미션에서는 **읽기 좋은 코드(Readable Code)**를 작성하는 것을 목표로 했다. 특히, OCP 원칙을 적용하여 결제 방식 추가 시 기존 코드를 수정하지 않도록 리팩토링하는 과정이 가장 기억에 남는다.✅ 접근 방법기존 코드에서는 if-else 문으로 결제 방식을 처리하고 있었다.새로운 결제 방식이 추가될 경우 기존 코드를 수정해야 하는 구조였다.이를 해결하기 위해 PaymentStrategy 인터페이스를 생성하고, 결제 방식별 클래스를 따로 만들었다.Order 클래스가 특정 결제 방식에 의존하지 않도록 설계했다.🛠 해결 과정처음에는 단순히 인터페이스를 분리하는 것이 OCP를 적용하는 것이라고 생각했다.그러나 실제로는 객체가 어떻게 생성되고 주입되는지까지 고려해야 OCP를 제대로 적용할 수 있음을 깨달았다.최종적으로 DI(의존성 주입)를 활용하여 Order 클래스가 결제 방식을 직접 관리하지 않도록 수정했다.🔥 미션 회고처음부터 완벽하게 원칙을 적용하려 하기보다, 작은 단위로 적용해보는 것이 효과적이었다.리팩토링 과정에서 인터페이스 분리만으로는 해결되지 않는 문제들이 있음을 깨닫고, 더 깊이 고민할 수 있었다.예전에는 단순히 돌아가는 코드에 집중했다면, 이제는 유지보수와 확장성을 함께 고려하게 되었다.💡 배운 점OCP는 단순히 인터페이스를 만드는 것이 아니라 기능 추가 시 기존 코드 수정이 필요 없는 구조를 만드는 것이 핵심이다.DIP를 적용할 때, 생성자 주입을 활용하면 유연한 코드 설계가 가능하다.처음부터 완벽한 구조를 만들려고 하기보다, 점진적으로 개선하는 것이 현실적이다.📌 앞으로 코드를 작성할 때 고려해야 할 점새로운 기능 추가 시 OCP를 완벽히 준수하는지 점검하기다른 사람들의 코드도 분석하며 다양한 접근법을 학습하기이번 주는 단순한 코드 작성을 넘어, 설계를 고민하는 습관을 기르는 과정이었다. 한 주가 지나고 나니, 처음보다 훨씬 더 깊이 있는 시각을 가지게 된 것 같아 뿌듯하다.다음 주도 꾸준히 성장하는 한 주가 되길 바라며, 더 나은 코드와 사고방식을 갖추도록 노력해야겠다!