블로그

sein

[워밍업 클럽 4기 - 백엔드] 2주차 발자국

2주차 회고[Day 6] 리팩토링: 코드 다듬기이번 챕터에서 제일 기억에 남는 건 '주석'이었다. 주석 사용 법은 배웠지만 좋은 주석은 처음 알게된 개념이라 도움이 많이 됐다. 실무에서도 개발하기 급급해서 기존에 있던 주석까지 신경쓰지 않았었는데 주석과 코드는 한 몸이라 생각하고 앞으로 꼼꼼히 봐야겠다고 생각했다.그리고 키워드 정리 시간에 지뢰찾기에 앞으로 추가해볼 수 있는 기능들을 몇몇개 말해주셨는데 내 힘으로 기능들을 추가해보는 시간을 가져도 좋을 것 같다. [Day 7] 클린코드 리팩토링 실습(미션)이번 챕터는 온전히 미션을 위한 날이었다. studycafe 패키지에 작성된 코드를 읽어보고, 추상화 해보는 미션을 진행했다.고작 조각 코드인 몇 줄 짜리가 아니라 더 어렵긴 했지만 더 생각할 수 있었던 것들이 많아서 좋았다.코드를 다 읽어보고 난 후에 여태 배운 추상화 기법들이 머릿속을 스쳐지나갔다. 이 부분에서 뭘 하면 좋을 것 같은데?! 정도의 생각이 들었다. 다만, 구현으로 실천하기가 매우 어려웠다. 생각과 구현 능력이 절실히 필요하다고 생각했다. 시간 상의 문제로 완벽히 추상화를 하진 못했지만 그래도 백지에서 추상화를 진행해본적이 처음이라 어렵지만 재밌었던 시간이었다. 추후 중간 점검 때 코드 리뷰에서 정말 많은 것을 얻었다. 각자의 실력도 다르지만 각자의 생각도 다르고, 관점도 달랐다. 그래서 배운 게 두 배, 아니 세 배 더 많아진 것 같다! [Day 8] 리팩토링 연습 | 기억하면 좋은 조언들studyCafe를 처음부터 끝까지 우빈님과 함께 진행했다. 순전히 내 뇌로는 닿을 수 없는 부분까지 닿을 수 있었던 거 같았다. 이래서 '나를 이끌어 줄 상사가 중요하구나'를 느꼈다.추상화도 어렵지만 오버 리팩토링이 너무 어려운 것 같다. 오버 리팩토링이 되는 시점을 정확히 캐치할 수 없다보니 그냥 둥둥 뇌 속에 떠도는 구름 같은 개념을 보는 것 같다. 우빈님이 말씀하신대로 오버 리팩토링은 경험에 의해서만 얻을 수 있다고하니 추상화를 많이 도전해보는 것이 중요하다고 느꼈다.실무에서도 추상화를 많이 접할 수 있으면 더 좋았을 것 같은데 그러지 못한 환경에 조금 갈증을 느끼며 오늘도 이직 단단히 다짐한다. Readable Code : 읽기 좋은 코드를 작성하는 사고법 강의가 막을 내렸다. 혼자서 도전했으면 어려움에 어지럼증을 느껴 끝까지 완주하지 못했을 것 같은 강의었다. 워밍업 클럽 덕분에 포기하지 않고 한 강의를 완강할 수 있어서 참으로 감사함을 느낀다.  [Day 9] 단위 테스트 | TDD | 테스트는 []다새로운 Practical Testing 테스트 강의를 시작했다!이번 챕터에서는 기본적인 테스트에 대해서 알게 되었고, TDD, BDD에 대해 얘기 나누었다. TDD는 말로만 들어봤었는데 강의에서 TDD 방식으로 코드를 짜나간다고 하시니 약간 생각지도 못한 이득 본 것 같아 엄지를 들었다.  강의를 계속 들으면서 느끼는 점은 코딩 실력도 중요하지만 언어 능력이 중요하다는 걸 엄청나게 많이 깨닫는다. 코딩은 사람이 컴퓨터에게 하는 언어라 컴퓨터가 못 알아 들으면 에러도 내주고 테스트 통과도 시켜주는데 사람이 하는 언어는 내가 뱉은 말을 정말 다양하게 해석할 수 있는 여지가 있어 정확하고 확실한 언어로 내 생각과 코드를 전달해야 정말 실력 있는 개발자인 것 같다.앞으로 그런 개발자가 되도록 노력하자!  출처인프런_워밍업_클럽_4기_BEReadable Code: 읽기 좋은 코드를 작성하는 사고법Practical Testing: 실용적인 테스트 가이드

백엔드워밍업클럽백엔드박우빈클린코드테스트

suover

인프런 워밍업 클럽 스터디 3기 - 백엔드 클린 코드, 테스트 코드 후기

Readable Code: 읽기 좋은 코드를 작성하는 사고법Practical Testing: 실용적인 테스트 가이드강의와 함께한 인프런 워밍업 클럽 스터디 3기 - 백엔드 클린 코드, 테스트 코드 (Java, Spring Boot) 후기 입니다.소개이번 스터디 3기는 백엔드 클린 코드, 테스트 코드를 주제로 진행하였습니다. “Readable Code: 읽기 좋은 코드를 작성하는 사고법”과 “Practical Testing: 실용적인 테스트 가이드” 강의를 함께 학습함으로써, 더 나은 코드 가독성과 안정적인 테스트를 위한 다양한 방법론을 실습할 수 있었습니다.지난 2기 때 Kotlin과 Spring Boot로 백엔드 개발 전반을 경험하며 웹 애플리케이션 동작 원리를 학습했다면, 이번 3기에서는 코드 품질과 테스트라는 핵심 주제를 깊이 파고들어, 보다 깨끗하고 유지보수하기 좋은 코드, 그리고 신뢰도 높은 테스트 코드를 작성하는 법을 익혔습니다.아래에서는 각 주차별 학습 내용을 정리하고, 전체 스터디를 통해 느낀 점과 앞으로의 계획을 공유하고자 합니다. 1주차: 클린 코드를 위한 기초 - 추상과 구체, SOLID 원칙1주차 주요 학습 내용추상과 구체의 개념논리와 사고의 흐름SOLID 원칙 (SRP, OCP, LSP, ISP, DIP)리팩토링 미션 수행1주차에서는 코드 가독성을 높이는 가장 기본이 되는 개념들을 배웠습니다. 특히 추상과 구체의 균형을 어떻게 잡아야 코드가 명확해지고, 협업자가 빠르게 이해할 수 있는지 고민할 수 있었습니다.추상은 핵심 개념과 의도만 드러내도록 하는 장점이 있지만, 너무 과도하면 실제 구현과 괴리가 생겨 혼란을 줄 수 있다는 점을 배웠습니다.구체는 세세한 내용을 모두 표현해 직관적이지만, 과도해지면 복잡해지고 가독성이 떨어질 수 있으므로 주의가 필요했습니다.또한 Early Return이나 부정어 최소화 같은 리팩토링 기법을 적용해 보면서, 간단한 습관만으로도 코드의 가독성이 크게 높아진다는 점을 실감했습니다. 2주차: 코드 구조 및 리팩토링 - 패키지 구조, 주석, 적정 기술2주차 주요 학습 내용효과적인 주석 사용 방식변수와 메서드의 나열 순서패키지 구조 설계와 적정 기술(Over Engineering 지양)스터디카페 이용권 시스템 리팩토링2주차에서는 코드 구조 전반을 깔끔하게 정비하는 방법을 중심으로 학습했습니다.주석은 변동이 잦거나 오해를 일으킬 만한 내용을 담지 말고, 정말 필요할 때만 사용해야 한다는 것을 다시금 확인했습니다.변수와 메서드를 사용하는 순서대로 배치하고, 공개 메서드(public) → 비공개 메서드(private) 순서로 자연스럽게 구성함으로써, 읽는 흐름을 방해하지 않는 것이 중요하다는 점을 배웠습니다.특히 리팩토링 미션을 통해 패키지 구조가 지나치게 단순하거나, 반대로 너무 세분화되어 관리가 어렵지 않은지 돌아볼 수 있었습니다. 적정 기술을 사용하는 태도. 즉, 문제를 해결하는 데 필요한 만큼만 기술 스택과 디자인 패턴을 적용하는 것이 얼마나 중요한지도 체감하게 되었습니다. 3주차: Spring & JPA, 계층형 아키텍처, 통합 테스트3주차 주요 학습 내용Spring & JPA를 이용한 계층형 아키텍처 (Controller, Service, Repository)단위 테스트와 통합 테스트의 차이점 및 작성 방법MockMvc, @DataJpaTest 등을 활용한 테스트 실습Bean Validation과 ExceptionHandler로 유효성 검증 및 예외 처리3주차부터는 실무에서 흔히 쓰이는 Spring & JPA 기반의 계층형 아키텍처에 초점을 맞춰, 전체적인 흐름을 학습했습니다.Layered ArchitecturePresentation Layer(Controller)Business Layer(Service)Persistence Layer(Repository)이러한 구조를 바탕으로, MockMvc를 사용해 Controller부터 Service, Repository까지 이어지는 통합 테스트를 작성해 봄으로써, 애플리케이션 전반이 정상 동작하는지 확인할 수 있었습니다.또한 @NotNull, @Positive 등 Bean Validation 애너테이션과 @RestControllerAdvice를 통한 예외 처리 방식도 연습해보았습니다. 4주차: Mock, Spy, @MockBean / @SpyBean, 레이어별 테스트 전략4주차 주요 학습 내용Mockito에서의 @Mock, @Spy, @InjectMocks스프링 환경에서의 @MockBean, @SpyBean레이어별 테스트 설계: 단위 테스트 vs 통합 테스트테스트 시나리오 설계(given-when-then)와 @BeforeEach 활용마지막 주차에서는 테스트 유지보수와 테스트 코드 설계의 중요성을 배웠습니다.@Mock, @Spy를 통해 순수 단위 테스트 환경에서 외부 의존성을 제거(또는 부분 제거)하여 빠른 피드백을 받을 수 있었습니다.반면 @MockBean, @SpyBean은 스프링 애플리케이션 컨텍스트를 띄운 상태에서 기존 Bean을 모의 객체로 교체할 수 있어, 좀 더 넓은 범위의 통합 테스트에 활용할 수 있었습니다.레이어별 테스트 (Controller, Service, Repository)를 분리해 진행하면 테스트가 더욱 명확해지고, 문제 발생 시 어느 레이어가 원인인지 빠르게 파악할 수 있음을 깨달았습니다.가장 크게 느낀 점은 ‘테스트도 코드의 일부’라는 사실이었습니다. 테스트 코드를 지속적으로 관리하고 개선해야, 유지보수가 필요한 시점에 믿을 수 있는 안전망이 되어준다는 것을 명확히 알게 되었습니다.회고이번 스터디 3기를 통해 "클린 코드와 테스트"라는 주제의 중요성을 다시 한번 실감했습니다. 단순히 기능만 구현하는 것이 아닌, 가독성, 유지보수성, 테스트 커버리지까지 고려해야 진정한 의미의 소프트웨어 품질을 높일 수 있음을 배웠습니다.아쉬웠던 점 & 보완 계획실프로젝트 적용: 강의와 미션 중심의 학습이기에, 실제 현업 수준의 복잡도를 가진 코드에 적용할 기회가 많지 않았습니다. 앞으로 개인/팀 프로젝트에서 적극 활용해 경험치를 높이고자 합니다.테스트 시나리오 다양성: 해피 케이스 중심의 테스트가 많았기에, 예외 상황과 경계값 테스트를 더 폭넓게 다루면 좋았을 것 같습니다. 마무리인프런 워밍업 클럽 스터디 3기를 통해, 클린 코드와 테스트 코드 작성 역량을 크게 향상시킬 수 있었습니다.코드: 명료한 이름 짓기, 중복 제거, 적절한 추상화 등을 통해 읽기 좋고 변화에 강한 코드를 추구하게 되었습니다.테스트: 프로젝트가 확장되더라도, 안정성을 확보할 수 있는 테스트 설계가 얼마나 중요한지 다시금 느끼게 되었습니다. 이번 스터디에서 성실히 참여한 결과, 2기에 이어 3기에서도 우수러너로 선정되는 영광도 누릴 수 있었습니다. 앞으로도 스스로 레거시 코드를 리팩토링하거나, 프로젝트를 확장하면서 배운 내용을 꾸준히 적용할 것입니다. 스터디에서 함께한 분들, 그리고 인프런과 워밍업 클럽 관계자분들께 감사드리며, 지속적인 학습과 공유를 통해 더 나은 개발자가 되도록 노력하겠습니다. 감사합니다!

백엔드인프런인프런워밍업클럽스터디3기워밍업백엔드후기클린테스트코드

suover

인프런 워밍업 클럽 스터디 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 등 스프링 테스트 환경에서 자주 쓰이는 기법들을 정리하고 적용해 보았습니다. 학습 내용과 미션을 통해 각 레이어가 가진 의미와 책임이 더욱 또렷해졌고, 가짜 객체를 어떻게 잘 활용해야 하는지 감이 잡힌 것 같습니다.앞으로도 학습 내용을 기록하고 회고하면서, 점점 더 탄탄한 테스트 코드를 작성해 가도록 하겠습니다.감사합니다!

백엔드인프런워밍업클럽스터디백엔드클린테스트코드발자국회고3기

워밍업 클럽 3기 BE 클린코드&테스트 - 4주차 발자국

Day 16섹션6. Spring & JPA 기반 테스트Presentation Layer 테스트 (2)모킹: 가짜 객체로 대신하여 정상 동작할거야 라는 것을 가정하고프레젠테이션 레이어즉, 테스트하고자 하는 레이어에만 집중해서 테스트하겠다모킹이 들어가는순간 단위테스트인것인가?Day 17섹션7. Mock을 마주하는 자세Mock 객체는 Mock의 역할, Stub의 역할을 동시에 할 수 있다고 한다.나도 Classicist 쪽에 가까운 것 같다.모킹이 100% 재현할 것이라는 것에 항상 의심을 하기 때문이다.Day 18섹션8. 더 나은 테스트를 작성하기 위한 구체적 조언한 문단에 한 주제다, 하나의 테스트는 하나의 주제만을 가져야 된다테스트 환경의 독립성을 보장하고, 테스트 간 독립성을 보장하자테스트 수행도 비용이다. 테스트 마다 Spring Boot가 뜨는것을 줄이기 위해 환경을 통합하자.Day 19드디어 강의를 다 들었다.Spring REST Docs, Swagger 둘 다 일장일단이 있는 것 같아서 팀의 내부사정에 맞게 사용하면 될 듯 싶다.정말 의미있는 시간이었다.Day 20중간 점검미션Day 16Layered Architecture 구조의 레이어별 테스트 작성법을 알아보았습니다. 레이어별로 1) 어떤 특징이 있고, 2) 어떻게 테스트를 하면 좋을지, 자기만의 언어로 다시 한번 정리해 볼까요?Persistence Layer특징: 데이터를 직접 접근하는 계층어떻게 테스트를 하면 좋을지: CRUD에만 집중하여 테스트하자. 비즈니스 로직이 들어가면 안된다.단위테스트같은 통합테스트로 진행하자. 수행 후 데이터를 잘 지워주자.Business Layer특징: 비즈니스 로직이 전개되는 계층어떻게 테스트를 하면 좋을지: 비즈니스 로직이 정상적으로 수행되는지 통합테스트로 진행하자. 수행 후 데이터를 잘 지워주자. 예외 케이스에 더 집중해야한다. 그것이 개발자의 역량이다.Presentation Layer특징: 외부에서 요청이 들어오는 계층어떻게 테스트를 하면 좋을지: 외부 파라미터에 대한 최소한의 유효성 검증을 진행하자. 하단 레이어는 모킹 처리하자. 진행하려는 유효성 검증이 어느 계층에서 검증해야 하는지 잘 생각해보자.Day 18@Mock, @MockBean, @Spy, @SpyBean, @InjectMocks 의 차이를 한번 정리해 봅시다.Mock: Mockito 단위 테스트에서 사용, 아무것도 지정하지 않으면 아무일도 일어나지않음. 행위를 검증MockBean: Spring Boot 테스트에서 사용, 스프링 컨테이너의 Bean을 목 객체로 교체Spy: 실제 객체를 기본으로 사용하고, 일부를 stubbing 가능SpyBean: Spring Boot 테스트에서 사용, 스프링 컨테이너의 Bean을 스파이 객체로 교체InjectMocks: 테스트 대상 객체에 @Mock, @Spy 객체들을 자동으로 주입아래 3개의 테스트가 있습니다. 내용을 살펴보고, 각 항목을 @BeforeEach, given절, when절에 배치한다면 어떻게 배치하고 싶으신가요? (@BeforeEach에 올라간 내용은 공통 항목으로 합칠 수 있습니다. ex. 1-1과 2-1을 하나로 합쳐서 @BeforeEach에 배치)개인적으로 테스트 환경의 독립성, 테스트 간 독립성을 보장하기 위해서 합치고 싶지 않습니다.모든 테스트에서 setUp 데이터를 사용할지라도, 나중에 추가될 테스트에서 해당 데이터를 사용할지 확신할 수 없고,또한 setUp에서 데이터를 만드는 것은 이 데이터가 어떤 테스트에서 필요한 것인지 명확하게 표현하지 못하는 것 같기 때문입니다.AfterEach로 데이터를 비워준다면 고려해볼 수는 있겠지만, 현재로서는 setUp에 합치는 방식이 적절하지 않다고 생각합니다. 끝났다.테스트 코드에 지레 겁을 먹지않게 해준 시간이었다.우리 모두 파이팅

백엔드백엔드워밍업클럽테스트박우빈발자국

워밍업 클럽 3기 BE 클린코드 (4주차 발자국) - 아니 이게 그런거였어?

아니 이게 그런거였어?테스트코드를 작성했었던 이전에는 GPT에게 가득 의존했다. @SpringBootTest의 통합환경에서 테스트를 작성하는데 아무것도 모르고 @Mock도 집어넣고... 그냥 대충 돌아가기만 하면 됐다~로 만족했었는데 이번 강의에서 어노테이션들의 존재 이유와 동작원리에 대해 이해하면서 작성할 수 있게 되었다. 이전에는 왜 찾아볼 생각도 안했었을까? 아마 찾을 시간도 없이 테스트코드를 바쁘게 작성하려고 했던 것이 원인이었겠지... 강의에서 배웠던 점앞서 말했던 여러 테스트용 어노테이션 뿐만 아니라, 테스트 코드를 작성하는 데에 자잘한 팁들을 얻어갔다.이전에 코드의 중복성을 제거하기위해 최대한 @BeforeEach에 코드를 넣어야지! 라고 생각했었었는데, 우빈님이 정확히 그부분을 짚어주셔서 뜨끔했었다. 뭐... 그건 과거니까.. 이제는 테스트 환경 혹은 테스트 사이의 독립성을 보장하는 것도 생각하면서 테스트를 작성할 수 있게 되지 않을까 생각한다.또한 "테스트도 비용이다." 라는 우빈님의 말도 생각난다. 이전에는 통합테스트를 여러번 돌릴 때 계속해서 스프링 서버가 띄워지게되면서 이전에 250개정도 되는 테스트를 돌릴때 대략 2분정도 걸려서 아 최적화를 해보고 싶은데 어떻게하지..? 라는 생각을 했었다.근데 우빈님이 하나의 스프링 서버 실행으로 여러 통합테스트를 같이 돌리는 기술(?)을 보여주셨는데 왜 그때 당시에 최적화하려고 노력하지 않았을까 하는 생각이 들었다. 생각보다 단순한 해결책일 수 있었는데.. 이외에도 평소에 궁금했던 private 메서드의 테스트 필요성에 대해서도 평소에 굉장히 궁금했었는데 우빈님의 말을듣고 마음이 편안해졌다. 또한 이전에 Clean Code를 배우고 오니, "private 메서드를 테스트 한다고 느끼면 책임을 분리해야 하는 시기가 온것이다." 라는 말에 크게 공감할 수 있었다.이 외에도 Spring Rest Docs라는 것도 배우고, 뜻 깊은 시간이었다. 고생하셨습니다!!

테스트

[인프런 워밍업 스터디] Day 18 미션

인프런 ‘Readable Code: 읽기 좋은 코드를 작성하는 사고법’을 수강한 후, 작성한 내용입니다.📌 @Mock, @MockBean, @Spy, @SpyBean, @InjectMocksMock과 Spy의 차이Mock: 행위에 대한 기대를 명세하고, 그에 따라 동작하도록 만들어진 객체Spy: Stub이면서 호출된 내용을 기록하여 보여줄 수 있는 객체일부는 실제 객체처럼 동작시키고 일부만 Stubbing할 수도 있다.Stub이란?테스트에서 요청한 것에 대해 미리 준비한 결과를 제공하는 객체. 그 외에는 응답하지 않는다.여기서 그러면 ‘Mock이랑 Stub이랑 무슨 차이지? 같은 내용같은데?’라는 의문이 들 수 있다.https://martinfowler.com/articles/mocksArentStubs.htmlstub은 상태 검증, mock은 행위 검증과 관련되어 있다.public interface MailService { public void send (Message msg); } public class MailServiceStub implements MailService { private List<Message> messages = new ArrayList<Message>(); public void send (Message msg) { messages.add(msg); } public int numberSent() { return messages.size(); } } // 테스트 public void testOrderSendsMailIfUnfilled() { Order order = new Order(TALISKER, 51); MailServiceStub mailer = new MailServiceStub(); order.setMailer(mailer); order.fill(warehouse); assertEquals(1, mailer.numberSent()); } Stub은 메일을 몇 번 보냈는지를 나타내는 상태를 검증한다.// 테스트 public void testOrderSendsMailIfUnfilled() { Order order = new Order(TALISKER, 51); Mock warehouse = mock(Warehouse.class); Mock mailer = mock(MailService.class); order.setMailer((MailService) mailer.proxy()); mailer.expects(once()).method("send"); warehouse.expects(once()).method("hasInventory") .withAnyArguments() .will(returnValue(false)); order.fill((Warehouse) warehouse.proxy()); } } Mock은 행위를 중심으로 검증한다.@Mock, @MockBean@Mock어노테이션이 붙은 객체를 Mock 객체로 생성@MockBean어노테이션이 붙은 객체를 Mock 객체로 생성하고, Spring Context에 등록된 빈을 Mock 객체로 대체한다.@Spy, SpyBean@Spy어노테이션이 붙은 객체를 실제 객체 기반으로 만들지만, 일부 기능만 Stubbing 할 때 사용@SpyBean해당 객체를 Spy 객체로 생성하고, Spring Context에 등록된 빈을 Spy 객체로 대체한다.@InjectMocks@InjectMocks 어노테이션이 붙은 객체가 필요로하는 필드 중 @Mock으로 생성된 객체를 주입해준다.📌 BDD 스타일로 테스트 코드 배치하기아래 3개의 테스트가 있다.@BeforeEach void setUp() { ❓ } @DisplayName("사용자가 댓글을 작성할 수 있다.") @Test void writeComment() { 1-1. 사용자 생성에 필요한 내용 준비 1-2. 사용자 생성 1-3. 게시물 생성에 필요한 내용 준비 1-4. 게시물 생성 1-5. 댓글 생성에 필요한 내용 준비 1-6. 댓글 생성 // given ❓ // when ❓ // then 검증 } @DisplayName("사용자가 댓글을 수정할 수 있다.") @Test void updateComment() { 2-1. 사용자 생성에 필요한 내용 준비 2-2. 사용자 생성 2-3. 게시물 생성에 필요한 내용 준비 2-4. 게시물 생성 2-5. 댓글 생성에 필요한 내용 준비 2-6. 댓글 생성 2-7. 댓글 수정 // given ❓ // when ❓ // then 검증 } @DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.") @Test void cannotUpdateCommentWhenUserIsNotWriter() { 3-1. 사용자1 생성에 필요한 내용 준비 3-2. 사용자1 생성 3-3. 사용자2 생성에 필요한 내용 준비 3-4. 사용자2 생성 3-5. 사용자1의 게시물 생성에 필요한 내용 준비 3-6. 사용자1의 게시물 생성 3-7. 사용자1의 댓글 생성에 필요한 내용 준비 3-8. 사용자1의 댓글 생성 3-9. 사용자2가 사용자1의 댓글 수정 시도 // given ❓ // when ❓ // then 검증 } 내용을 살펴보고, 각 항목을 @BeforeEach, given절, when절에 배치한다면 어떻게 배치해야 할까?(@BeforeEach에 올라간 내용은 공통 항목으로 합칠 수 있습니다. ex. 1-1과 2-1을 하나로 합쳐서 @BeforeEach에 배치)✔ 게시판 게시물에 달리는 댓글을 담당하는 Service Test✔ 댓글을 달기 위해서는 게시물과 사용자가 필요하다.✔ 게시물을 올리기 위해서는 사용자가 필요하다.직접 코드 배치해보기@BeforeEach void setUp() { 사용자 생성에 필요한 내용 준비 사용자 생성 사용자의 게시물 생성에 필요한 내용 준비 사용자의 게시물 생성 } @DisplayName("사용자가 댓글을 작성할 수 있다.") @Test void writeComment() { // given 댓글 생성에 필요한 내용 준비 // when 댓글 생성 // then 검증 } @DisplayName("사용자가 댓글을 수정할 수 있다.") @Test void updateComment() { // given 댓글 생성에 필요한 내용 준비 댓글 생성 // when 댓글 수정 // then 검증 } @DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.") @Test void cannotUpdateCommentWhenUserIsNotWriter() { // given 사용자2 생성에 필요한 내용 준비 사용자2 생성 사용자1의 댓글 생성에 필요한 내용 준비 사용자1의 댓글 생성 // when 사용자2가 사용자 1의 댓글 수정 시도 // then 검증 } 공통된 준비 작업을 @BeforeEach로 분리댓글을 달기 위한 조건 작업게시물을 작성할 사용자 생성게시물 생성given검증하고자 하는 행동에 필요한 작업 수행댓글 생성과 관련된 내용은 테스트마다 관리할 수 있도록 given 절에 배치when검증하고자 하는 행동

백엔드인프런워밍업스터디Mock테스트

Day 16 미션 : 레이어 아키텍쳐의 테스트

이번에 설명하는 레이어는 영속성 레이어, 비즈니스 레이어, 프레젠테이션 레이어를 기준으로 설명하겠습니다.레이어의 특징어떻게 테스트하면 좋을지영속성 레이어영속성 레이어?DB에 접근하는 계층비즈니스 로직이 들어가지 않은 순수하게 데이터에 대한 처리 및 조회를 수행영속성 레이어의 테스트무엇을 확인해야할까?원하는 데이터에 정확히 접근하는지쿼리가 길어졌을 때, 내가 원하는 데이터에 맞게 작성되었는지어떻게 테스트를 해야할까?영속성 계층이 의존하는 계층이 대부분 상황에서 없기 때문에 단위 테스트 형식으로 진행비즈니스 레이어비즈니스 레이어?비즈니스 로직이 전개되는 계층영속성 레이어가 사용된다.도메인 개념이 적용트랜잭션 개념 적용비즈니스 레이어의 테스트하나의 트랜잭션을 보장하는지 확인비즈니스 로직이 정확히 수행되는지 확인여러 케이스에 대해 테스트하자.프레젠테이션 레이어프레젠테이션 레이어란?외부 세계와 가장 가까운 계층요청과 관련한 데이터를 받는다.요청에 대한 데이터를 전달한다.프레젠테이션 레이어의 테스트요청에서 건너온 값들에 대한 검증도메인 규칙을 제외한 간단한 검증상황에 대한 정확한 응답이 반환되는지 확인

백엔드테스트레이어

suover

인프런 워밍업 클럽 스터디 3기 - 백엔드 클린 코드, 테스트 코드 3주차 발자국

Readable Code: 읽기 좋은 코드를 작성하는 사고법Practical Testing: 실용적인 테스트 가이드강의와 함께한 인프런 워밍업 클럽 스터디 3기 - 백엔드 클린 코드, 테스트 코드 (Java, Spring Boot)3주차 발자국 입니다.학습 내용 요약이번 주에는 Spring & JPA 환경에 계층형 아키텍처(Layered Architecture) 및 테스트 기법(통합 테스트, MockMvc, JPA 테스트 등)을 학습했습니다.1⃣ Spring & JPASpring이 제공하는 IoC/DI 개념과 ORM(JPA, Hibernate) 기반 데이터 접근 방식을 이해했습니다.엔티티(Entity) 설계를 통해 도메인 모델링을 하고, 리포지토리(Repository) 계층으로 데이터 CRUD를 담당하도록 분리했습니다. 2⃣ Layered ArchitecturePresentation Layer(Controller)Business Layer(Service)Persistence Layer(Repository)컨트롤러는 요청/응답을 처리하고, 비즈니스 로직은 서비스가 담당하며, 데이터 접근은 리포지토리를 통해 수행합니다.3⃣ 테스트 코드 작성단위 테스트: 특정 클래스나 메서드 단위 검증. 예: JUnit + AssertJ 사용통합 테스트: 스프링 컨텍스트를 로드해 여러 레이어(Controller, Service, Repository)의 협력을 검증.MockMvc를 사용해 테스트하는 방식을 배웠습니다.4⃣ 유효성 검증(Bean Validation)DTO에서 @NotEmpty, @NotNull, @Positive 등 애노테이션을 사용하여 입력값을 검증하고,RestControllerAdvice로 BindException 등을 처리하여 예외 상황 시 API 응답을 표준화했습니다.✨ 학습 회고Spring & JPA 학습을 통해, 계층형 아키텍처 전체 흐름을 이해해야 함을 느꼈습니다.테스트 코드에서, 단위 테스트와 통합 테스트의 경계와 필요성을 체감했습니다.JUnit5 + AssertJ로 간단한 메서드 단위 검증을 할 때는 편리했지만, MockMvc로 검증하거나, @DataJpaTest로 테스트할 때는 더 많은 설정이 필요했습니다. 그만큼 테스트 커버리지가 넓어지며 신뢰도가 높아진다는 장점을 느꼈습니다.Validation과 ExceptionHandler를 통해 잘못된 입력을 막고, 표준화된 에러 응답을 제공하는 프로세스가 얼마나 중요한지 깨달았습니다.미션 해결 과정🎯 스터디카페 이용권 선택 시스템 테스트 미션미션스터디카페 이용권 선택 시스템의 테스트 코드를 작성하는 미션.미션 목표메시지 출력 테스트할인 로직 테스트사물함 선택 테스트 해결 과정OutputHandlerTest환영 메시지 + 공지사항 “프리미엄 스터디카페” 등의 문구가 제대로 찍히는지 확인.주문 내역 요약에 “이용 내역”, “이용권”, “사물함”, “총 결제 금액” 같은 키워드가 포함되는지 검증.StudyCafePassOrderTest할인율 및 가격 계산이 의도대로 동작하는지 (예: (250,000 + 10,000) - 할인액 25,000 = 235,000) 확인.사물함이 있는지 없는지에 따라 Optional<LockerPass>가 올바르게 반환되는지 테스트.StudyCafeSeatPassTest할인율이 0일 때 할인 금액이 0원인지, 0.1이면 정상 할인액이 계산되는지.시간권(HOURLY)은 cannotUseLocker() == true, 고정석(FIXED)은 false인지 등을 테스트로 보장. 회고도메인 로직이 잘 분리되어 있어 테스트 작성이 수월했습니다. 만약 복잡한 코드가 여기저기 있었다면, 테스트도 훨씬 어렵고 중복될 뻔했습니다. 또한 예외 처리 부분에 대한 테스트 코드의 필요성도 느껴졌습니다. 예외 상황에 대한 흐름을 명확하게 검증하고, 안정적인 동작을 보장하기 위해서는 예외 처리에 대한 테스트도 함께 구성되어야 한다고 생각했습니다.🔗 테스트 미션 깃허브 링크회고스스로 칭찬하고 싶은 점도메인 흐름 파악: Controller → Service → Repository 순으로 데이터가 흐르는 구조를 더욱 제대로 이해하게 되었습니다. 다양한 스프링 테스트 어노테이션(WebMvcTest, SpringBootTest, DataJpaTest 등)을 직접 적용하며, 각 용도에 맞게 테스트를 구분해본 경험이 유익했습니다.아쉬웠던 점 & 보완할 점테스트 격리: 통합 테스트 시 상태를 초기화하는 과정이 번거로웠고, 테스트 순서에 따라 의존성이 생기지 않도록 더 철저히 관리해야 함을 느꼈습니다.테스트 시나리오 다양성 부족: 대부분 해피 케이스 위주로 검증한 감이 있어, 더욱 폭넓은 예외 상황에 대한 시나리오를 추가 작성하면 안정성이 높아질 것 같습니다.다음 주 학습 목표Mock과 Test Double: Mock과Test Double 개념을 학습하고, 적절히 사용해 볼 예정입니다.테스트 환경 독립성:테스트 시 독립적인 환경을 유지하는 방법을 살펴볼 예정입니다.테스트 개선 기법:조금 더 간결하고 가독성 높은 테스트 작성을 시도해 볼 계획입니다.이번 주에는 Spring & JPA 환경에서의 테스트 코드 작성을 중점적으로 경험 했습니다. 이를 통해 서비스가 단계적으로 확장되더라도, 테스트 코드가 안정적인 작동을 보장할 수 있다는 점을 다시금 깨달았습니다. 앞으로도 테스트 우선 방식으로 새로운 기능이나 리팩토링을 진행해, 안전하고 효율적인 협업 환경을 유지해 나가겠습니다.감사합니다!

백엔드인프런워밍업클럽스터디백엔드클린테스트코드발자국회고3기

워밍업 클럽 3기 BE 클린코드&테스트 - 3주차 발자국

Day 12섹션6. Spring & JPA 기반 테스트Persistence Layer 테스트 (1)Persistence Layer 테스트 (2)DataJpaTest는 JPA 관련 빈들만 등록해주기 때문에 SpringBootTest보다 빠르다@ActiveProfiles 어노테이션을 사용해 활성화 할 프로파일을 설정할 수 있다.리스트를 검증할 때는 size를 먼저 검증하고extracting + contains 조합을 많이 사용한다extracting 내부에는 검증할 필드들을 넣어주면되고contains는 다양한 api들이 있다.여기서 사용한 containsExactlyInAnyOrder()는 순서 상관없이 인자로 들어온 튜플들이 정확히 존재하는지extracting에 적은 필드의 순서대로 적어주면 된다.Day 13Business Layer 테스트 (1)Persistence Layer는 비즈니스 가공 로직이 포함되어서는 안 된다.Business Layer는 Persistence Layer와의 상호작용(Data를 읽고 쓰는 행위)을 통해 비즈니스 로직을 전개Business Layer 테스트 (2)SpringBootTest는 자동롤백이 안달려있고DataJpaTest는 자동롤백이 달려있음Day 14Business Layer 테스트 (3)Day 15Presentation Layer 테스트 (1)모킹: 가짜 객체로 대신하여 정상 동작할거야 라는 것을 가정하고프레젠테이션 레이어즉, 테스트하고자 하는 레이어에만 집중해서 테스트하겠다readOnly = true : 읽기전용 트랜잭션CRD 작업이 동작 X / only ReadJPA : CUD 스냅샷 저장, 변경감지 등을 안해도 되기 때문에 성능 향상CQRS - Command(CUD) / Query(R)미션Day 11사용자의 입력값은 무조건 불신을 깔고 들어가야한다는 강사님의 말씀이 떠올랐다.그래서 InputHandler를 테스트하고자 했다.StudyCafeIOHandler를 테스트해야하나 고민했지만, 통합해주는 역할일뿐입력에 대한 최종 책임은 InputHandler에 있다고 판단했다.기존 InputHandler에는private static final Scanner SCANNER = new Scanner(System.in);로 스캐너가 생성되어있어서 테스트할 때 nextLine() 예외가 발생했다.private final Scanner scanner; public InputHandler(Scanner scanner) { this.scanner = scanner; }사용자 입력같은 테스트하기 어려운 영역을 분리하자는 강사님의 말씀이 떠올라 외부에서 주입받도록InputHandler를 변경해주었다.또 해피 케이스말고 예외 케이스를 생각해 1~3 이외에 다른 입력시 들어왔을 때 예외가 잘 발생하는지 확인했다.또 사물함을 사용할 수 있는 패스권인지 확인하는 메서드를 검증하기 위해StudyCafePassType에 대한 테스트를 진행했다.확실히 enum 타입으로 객체로 만드니 관련 로직을 위한 공간이 생겨 테스트가 용이하구나를 느꼈다.그리고 가장 중요한 금액관련 테스트를 진행하고자했다.금액 관련 테스트를 StudyCafePassOrder에서 전부 다 진행할까 했지만,하나의 테스트는 하나의 책임만 가져야한다고 생각해서,StudyCafeSeatPass, StudyCafeLockerPass에서 각각 진행했다.테스트를 진행하면서 궁금한 점은StudyCafeLockerPass lockerPass = StudyCafeLockerPass.of(StudyCafePassType.FIXED, 4, 10000);처럼 of 메서드에서 csv 파일에 맞게 직접 입력해주는게 맞을지아니면 LockerPassFileReader에서 객체를 찾아내서 테스트 하는게 맞을지 궁금하다.그리고 DisplayName 짓는 것이 생각보다 되게 까다로웠다.테스트는 쉽지않다. 하지만 올바르지 못한 테스트는 오히려 혼란을 일으킬뿐이라는 것은 알게되었다.제대로 된 테스트를 작성하도록 노력하자.모킹 테스트를 왜 해야는지는 아직 명확하게 와닿진 않는다.알 때 까지 복습하자.

백엔드워밍업클럽테스트발자국박우빈

suover

인프런 워밍업 클럽 스터디 3기 - 백엔드 클린 코드, 테스트 코드 2주차 발자국

Readable Code: 읽기 좋은 코드를 작성하는 사고법Practical Testing: 실용적인 테스트 가이드강의와 함께한 인프런 워밍업 클럽 스터디 3기 - 백엔드 클린 코드, 테스트 코드 (Java, Spring Boot)2주차 발자국 입니다.학습 내용 요약이번 주는 클린 코드 작성법에 이어 코드 구조와 리팩토링, 그리고 테스트 코드 작성법을 본격적으로 학습했습니다.1⃣ 주석의 양면성주석은 코드 자체로 표현하기 어려운 의사결정의 히스토리를 기록할 때 사용해야 합니다.자주 변하는 정보는 주석으로 표현하면 안 되고, 주석은 코드와 함께 꾸준히 관리해야 합니다.부정확한 주석은 주석이 없는 코드보다 더 해롭습니다.2⃣ 변수와 메서드의 나열 순서변수는 사용하는 순서대로 나열하고, 공개 메서드를 먼저 배치하여 외부 세계에 제공하는 기능을 명확히 드러냅니다.비공개 메서드는 공개 메서드에서 호출되는 순서로 나열하여 코드 읽기 흐름을 자연스럽게 만듭니다.나열 순서도 의도를 표현하는 중요한 수단이 될 수 있음을 배웠습니다.3⃣ 패키지 나누기패키지 구조는 도메인 문맥을 반영하여 설계하며, 너무 잘게 나누거나 너무 크게 뭉치는 것을 지양해야 합니다.팀 내 합의가 중요하며 초기 설계 단계에서 패키지 구조를 충분히 고민해야 합니다.4⃣ 적정 기술의 사용모든 기술은 적정한 수준과 시점에서 활용해야 하며, 무리한 추상화나 오버 엔지니어링은 오히려 개발 효율성을 떨어뜨립니다.클린 코드는 만능 해결책이 아니며, 상황과 목적에 맞는 균형 잡힌 접근이 중요합니다.5⃣ 테스트 코드와 TDD자동화된 단위 테스트(JUnit5, AssertJ)를 통해 신뢰성과 유지보수성을 높이고, 빠른 피드백을 받을 수 있습니다.TDD(Test Driven Development)를 통한 선(先) 테스트 작성, 후(後) 기능 구현 방식을 이해했습니다.Given-When-Then 구조를 이용한 BDD(Behavior Driven Development) 스타일로 작성하면 도메인 로직을 명확히 표현할 수 있습니다.미션 해결 과정🎯 스터디카페 이용권 선택 시스템 리팩토링 미션미션'스터디 카페 이용권 선택 시스템'의 기존 시스템에 중복된 로직과 혼재된 책임을 명확히 분리하고, 객체지향 원칙을 준수하여 코드 구조를 개선하는 리팩토링 미션입니다.접근 관점추상화 레벨 맞추기: 중복 로직을 별도 메서드로 추출했습니다.객체의 책임 분리: StudyCafeOrder 객체를 추가해 할인 계산 로직을 전담했습니다.DIP(의존성 역전 원칙): 파일 접근 로직을 인터페이스(StudyCafeRepository)로 추상화하여 확장성을 확보했습니다.해결 과정1. 기존의 복잡한 분기 조건(if-else)을 메서드로 깔끔하게 정리했습니다.Before: if (HOURLY) { ... } else if (WEEKLY) { ... } else if (FIXED) { ... } 로직이 길고, 사물함 로직도 그 안에서 처리.After:public void run() { // 1) passType 선택 (시간권/주단위/고정석) StudyCafePassType passType = inputHandler.getPassTypeSelectingUserAction(); // 2) pass 선택 StudyCafePass selectedPass = selectPass(passType); // 3) 사물함 선택 (고정석 only) StudyCafeLockerPass lockerPass = maybeSelectLocker(selectedPass); // 4) 주문 객체 생성 & 결과 출력 StudyCafeOrder order = new StudyCafeOrder(selectedPass, lockerPass); outputHandler.showOrderResult(order); }이점: 각 단계가 메서드로 분리되어 이해하기 쉬움. passType별로 중복된 분기 로직이 크게 줄었음.      2. 할인 및 총금액 계산 로직을 전담하는 객체를 만들어 로직을 명확히 했습니다.Before: 할인금액, 총액 계산 로직이 OutputHandler.showPassOrderSummary 안에 위치.After: StudyCafeOrder라는 도메인 객체가 이 로직을 담당.public int getDiscountAmount() { return (int) (selectedPass.getPrice() * selectedPass.getDiscountRate()); } public int getTotalPrice() { int discountPrice = getDiscountAmount(); int lockerPrice = (lockerPass != null) ? lockerPass.getPrice() : 0; return selectedPass.getPrice() - discountPrice + lockerPrice; }이점: 필요 시 “할인 정책”이 바뀌어도 StudyCafeOrder만 수정하면 되며, 출력부는 깔끔하게 유지.   3. 파일 접근 방식을 인터페이스와 구현체로 나누어 유지보수와 확장성을 높였습니다.Before: StudyCafeFileHandler라는 클래스에서 직접 파일 접근 + 변환.After: StudyCafeRepository(추상) + StudyCafeFileRepository(구현).StudyCafePassMachine은 StudyCafeRepository repository에만 의존 → DIP 준수.향후 DBRepo나 InMemoryRepo 추가 시 StudyCafeRepository만 구현하면 됨. 회고리팩토링을 통해 코드 가독성과 유지보수성이 눈에 띄게 향상되었습니다. 특히 객체지향 원칙(SRP, DIP)을 철저히 적용한 결과, 각 객체가 단 하나의 명확한 책임을 가지면서, 향후 변경이 발생해도 최소한의 수정만으로 대응 가능해졌습니다. 규칙의 절대성이 아닌 "적정선"을 찾는 것이 매우 중요하다는 점을 깊이 깨달았습니다.🔗 리팩토링 미션 깃허브 링크회고스스로 칭찬하고 싶은 점학습한 내용을 즉시 코드에 적용하며 원칙을 체화하고자 노력한 점단순한 기능적 개선을 넘어, 구조적이고 근본적인 리팩토링을 고민한 점아쉬웠던 점 & 보완할 점코드 리뷰에서 코드 확장성과 유지보수성을 높이기 위한 설계가 부족했음을 느꼈습니다. 다음에는 코드 확장성과 유지보수성을 고려한 구조를 더욱 더 고민해 보겠습니다.지나치게 추상화에 치우치는 경향을 느꼈고, 다음에는 더 실용적인 균형을 유지하려 합니다.다음 주 학습 목표다음 주부터는 TDD 기반으로 더욱 실질적이고 구체적인 테스트 코드 작성을 연습할 계획입니다.리팩토링과 테스트가 결합된 실습을 통해, 코드를 더욱 안정적으로 관리하는 방법을 익힐 예정입니다.이번 주 스터디를 통해 "코드는 언제나 적정한 수준의 추상과 구체 사이에서 균형을 잡아야 한다"는 중요한 원칙을 다시 한번 실감했습니다. 앞으로도 적절한 추상화와 구체적인 구현 사이의 "적정선"을 끊임없이 고민하며, 클린하고 안정적인 코드를 지속적으로 작성하는 개발자가 되도록 노력하겠습니다.감사합니다!

백엔드인프런워밍업클럽스터디백엔드클린테스트코드발자국회고3기

[2주차 발자국] Readable Code 적용기

인프런 ‘Readable Code: 읽기 좋은 코드를 작성하는 사고법’을 수강한 후, 작성한 내용입니다.📌 2주차 강의 (Readable Code)코드 다듬기주석의 양면성주석이 많다 ⇒ 비즈니스 요구사항을 코드에 잘 녹이지 못했나 의심해보자.추상화로 설명이 덜 된 것은 아닐까?주석에 의존하면 적절하지 않은 추상화 레벨을 가지게 된다.좋은 주석우리가 가진 모든 표현 방법을 총동원해 코드에 의도를 녹여내고, 그럼에도 불구하고 전달해야 할 정보가 남았을 때 사용하는 주석의사 결정의 히스토리를 도저히 코드로 표현할 수 없을 때, 주석으로 상세하게 설명!번수와 메서드의 나열 순서변수는 사용하는 순서대로 나열하자.인지적 경제성메서드의 순서는 객체의 입장에서 생각해보자.객체는 외부 세계와 어떻게 소통할 것인지가 중요공개 메서드의 스펙을 통해 외부 세계와 소통객체는 협력을 위한 존재외부 세계에 내가 어떤 기능을 제공할 수 있는지를 드러낸다.공개 메서드끼리도 기준을 가져보자.상태 변경 > 판별 > 조회 메서드중요한 것은, 나열 순서로도 의도와 정보를 전달할 수 있다는 것!패키지 나누기패키지는 문맥으로서의 정보를 제공할 수 있다.패키지를 쪼개지 않으면 관리가 어렵다.너무 잘게 쪼개도 안됨대규모 패키지 변경은 팀원과의 합의를 이룬 시점에!기능 유지보수하기객체 지향적으로 책임이 잘 분리되어 있다면,문제가 되는 위치를 발견하여 수정하기 쉽다!알고리즘 교체와 같은 작업이 수월하다!IDE 도움 받기코드 포맷팅sonarlinteditconfig리팩토링 연습스스로 리팩토링을 진행해보고 비교해보는 시간이유를 가지고 리팩토링을 진행하자.변경 포인트는 그 이유를 명확하게 설명할 수 있어야 한다!!감으로 하는 리팩토링은 설득할 수 없는 코드가 될 확률이 높다.객체에 메시지를 보내자.객체의 책임과 응집도를 고려하자.기억하면 좋은 조언들능동적 읽기눈으로 복잡하게 보고 이해하려 애쓰는 것보다 직접 경험해보며 읽자.복잡하거나 엉망인 코드를 읽고 이해하려 할 때, 리팩토링하면서 읽기공백으로 구분메서드와 객체 추상화주석으로 이해한 내용 표기하며 읽기핵심 목표는 도메인 지식을 늘리는 것.그리고 이전 작성자의 의도를 파악하는 것.오버 엔지니어링필요한 적정 수준보다 더 높은 수준의 엔지니어링구현체가 하나인 인터페이스아키텍처 이해에 도움을 주거나, 근시일 내에 구현체가 추가될 가능성이 높다면 괜찮음구현체 수정 시, 인터페이스도 수정코드 탐색에 영향을 준다.너무 이른 추상화정보가 숨겨지기 때문에 복잡도가 높아짐은탄환은 없다만능 해결사 같은 기술은 없다.객체 지향적인 체스 프로그램but, 체스는 500년 동안 변하지 않았다.실무 : 2가지 사이의 줄다리기지속 가능한 소프트웨어의 품질 VS 기술 부채를 안고 가는 빠른 결과물모든 기술과 방법론은 적정 기술의 범위 내에서 사용되어야 한다.도구라는 것은, 한계까지 사용할 줄 아는 사람이 그것을 사용하지 말아야 할 때도 아는 법이다.📌 2주차 강의 (Practical Testing)Intro강의 소개무엇을 학습하는가?테스트 코드가 필요한 이유좋은 테스트 코드란 무엇일까?실무에서 진행하는 방식 그대로 테스트를 작성해가면서 API를 설계하고 개발하는 방법구체적인 이유에 근거한 상세한 테스트 작성 팁어떻게 학습하면 좋을까?무엇을 모르는지 아는 것 = 찾아볼 수 있게 된다는 것선택과 집중을 통해 아는 영역을 넓혀가고, 익숙하지 않은 것들을 익숙하게 하고, 들어보지 못했던 것을 들으면서 나에게 노출시키자.인덱스를 통해 영역을 넓혀가자.테스트는 왜 필요할까?테스트 코드를 작성하지 않는다면,변화가 생기는 순간마다 발생할 수 있는 모든 Case 고려해야 한다.변화가 생기는 순간마다 모든 팀원이 동일한 고민을 해야 한다.빠르게 변화하는 소프트웨어의 안정성 보장 X테스트 코드가 병목이 된다면,프로덕션 코드의 안정성 보장 X테스트 코드 자체가 유지보수하기 어려운 짐잘못된 검증이 이루어질 가능성 O테스트를 통해 얻고자 하는 것 ⇒ 빠른 피드백, 자동화, 안정감따라서, 올바른 테스트 코드는자동화 테스트로 빠른 시간에 버그를 발견하고 비용을 절약한다.소프트웨어의 빠른 변화를 지원한다.팀원들의 집단 지성을 팀 차원의 이익으로 승격시킨다.단위 테스트수동 테스트 vs 자동화된 테스트콘솔에 찍힌 내용을 보고 판단하는 테스트최종 단계에서 사람이 개입해야 함다른 사람이 봤을 때, 무엇을 검증하고 어떤 것이 맞는 상황인지 알 수 없다.과연 이것이 자동화된 테스트일까?JUnit5로 테스트하기단위 테스트작은 코드 단위를 독립적으로 검증하는 테스트작은 코드 = 클래스, 메서드독립적 = 외부 상황에 의존적이지 않아야 한다.검증 속도가 빠르고, 안정적이다.JUnit 5단위 테스트를 위한 테스트 프레임워크AssertJ테스트 코드 작성을 원활하게 돕는 테스트 라이브러리풍부한 API, 메서드 체이닝 지원리스트 사이즈 검증할 때 다음을 이용하자..hasSize().isEmpty()테스트 케이스 세분화하기해피 케이스예외 케이스assertThatThrownBy()경계값 테스트범위, 구간, 날짜 등테스트하기 어려운 영역 분리하기생성 시간 검증에 대한 테스트에서 생성 시간을 메서드 내부에서 측정한다면?테스트하는 시간에 따라 테스트 결과가 달라진다.외부로 분리하자!외부로 분리할수록 테스트 가능한 코드는 많아진다.테스트하기 어려운 영역관측할 때마다 다른 값에 의존하는 코드현재 시간에 의존하는 코드외부 세계에 영향을 주는 코드테스트 하기 좋은 영역 (순수함수)같은 입력에는 항상 같은 결과외부 세상과 단절된 형태TDD : Test Driven Development프로덕션 코드보다 테스트 코드를 먼저 작성하여 테스트가 구현 과정을 주도하도록 하는 방법론RED → GREEN → REFACTORRED : 프로덕션 코드 없이 테스트를 먼저 작성GREEN : 테스트가 통과할 수 있는 최소한의 코딩REFACTOR : 테스트 통과를 유지하면서 구현 코드 개선기능 구현 후, 테스트를 작성하면,테스트 자체의 누락 가능성특정 테스트 케이스만 검증할 가능성잘못된 구현을 다소 늦게 발견할 가능성테스트를 먼저 작성하면,복잡도가 낮은, 테스트 가능한 코드로 구현쉽게 발견하기 어려운 엣지 케이스를 놓치지 않게 해준다.구현에 대한 빠른 피드백과감한 리팩토링테스트를 구현부 검증을 위한 보조 수단이 아니라, 테스트와 상호 작용하며 발전하는 프로덕션 코드을 만든다고 바라본다.즉, 객체 사용자 관점에서의 피드백을 준다!추가 인덱스애자일 방법론일정한 주기를 가지고 빠르게 제품을 출시하여 고객의 요구사항, 변화된 환경에 맞게 요구를 더하고 수정해나가는 방법익스트림 프로그래밍빠른 개발 속도를 유지하며 고객이 원하는 요구들을 지속적으로 피드백하는 방법의사소통, 단순성, 용기, 피드백, 존중하나의 방법론 : TDD스크럼, 칸반스크럼 : 스프린트라는 일정기간 안에 완료할 수 있는 작업으로 업무를 분할한다.칸반 : 동시에 개발이 진행될 수 있는 아이템의 수를 제한하여, 업무의 병목 현상과 리소스 낭비를 처리할 수 있도록 한다.테스트는 [ ]다.테스트는 [문서]다.프로덕션 기능을 설명하는 테스트 코드 문서다양한 테스트 케이스를 통해 프로덕션 코드를 이해하는 시각과 관점 보완과거에 경험했던 고민의 결과물을 팀 차원으로 승격시켜, 모두의 자산으로 공유DisplayName을 섬세하게비교음료 1개 추가 테스트음료 1개 추가하면 주문 목록에 담긴다.명사의 나열보다 문장으로테스트 행위에 대한 결과까지 기술하기비교특정 시간 이전에 주문을 생성하면 실패한다.영업 시작 시간 이전에는 주문을 생성할 수 없다.도메인 용어를 사용하여 한층 추상화된 내용을 담기메서드 자체의 관점보다 도메인 정책 관점으로!테스트의 현상을 중점으로 기술하지 말 것BDD 스타일로 작성하기함수 단위의 테스트에 집중하기보다, 시나리오에 기반한 테스트케이스(TC) 자체에 집중하여 테스트개발자가 아닌 사람이 봐도 이해할 수 있을 정도의 추상화 레벨 권장Given / When / ThenGiven : 시나리오 진행에 필요한 모든 준비 과정When : 시나리오 행동 진행Then : 시나리오 진행에 대한 결과 명시, 검증명확하게 표현하지 못한 테스트는 나중에 우리의 사고를 제한하는 허들이 될 수 있다!추가 인덱스JUnit전통적인 Java 기반의 테스트 프레임워크어노테이션 사용SpockGroovy 기반의 BDD(Behavior-Driven Development) 프레임워크BDD 스타일 테스트로 가독성이 더 좋다.Groovy 언어로 작성해야하므로 작성이 어려울 것 같다.📌 Day 7 미션 & 중간 점검 라이브Day 7 미션 : 변경 포인트는 그 이유를 명확하게 설명할 수 있어야 한다!Day 7 미션은 ‘스스로 스터디 카페 이용권 선택 시스템 리팩토링해보기’였다.미션 수행 전 강의를 들으며 내용들에 대해 거부감 없이 받아들여졌고, 빨리 코드를 짜보고 싶다는 생각이 들도록 하였다. 하지만 실제로 코드를 작성하면서 고민되는 지점들도 꽤 많았고, 근거를 가지고 리팩토링을 하는 과정이 쉽지는 않았다. ‘이게 더 나은거 같은데…?’하며 감으로 진행한 부분도 꽤 있었던 것 같다. 미션을 진행하고 강의를 수강하면서 ‘변경 포인트는 그 이유를 명확하게 설명할 수 있어야 한다.’라는 말이 내가 유의해야 할 말로 느껴졌다.중간 점검 라이브중간 점검 라이브는 Day 4 미션 공통 피드백, Q&A, 코드 리뷰 순서로 진행되었다.Day 4 미션 공통 피드백Day 4 미션 공통 피드백 다음과 같다.추출한 메서드에 static이 있는 경우boolean을 return 하는 메서드에 예외 throw오버 엔지니어링나도 메서드를 추상화하기 위해 추출하다보면, IDE에서 static 키워드를 붙여서 따로 지워준 적이 있다. 추출하여 메서드명만 적지 말고, 전체적으로 확인해보자.상태를 체크하고 boolean을 반환하는 메서드가 있을 때 예외를 던지는 것 보다 상황에 맞게 리팩토링 하는게 좋은 것 같다. validateOrder에서 false를 반환해서 유효하지 않은 주문을 나타내면 되지 않을까?미션의 안내 사항을 잘못 이해한 것 같다. Order의 내부 구현 없이 단순한 리팩토링을 제안하는 것이 요구사항이다. 그런데 나는 ‘Order의 메서드를 추가하더라도 구현 내용은 안적어도 된다.’라고 이해했다. 미션에서 요구했던 내용은 Order에 추가적으로 구현되는 사항 없이 리팩토링을 진행하라고 했던 것 같다.Q&AQ&A의 많은 내용들이 나에게 도움이 되었다. 다른 분들께서 질문해주신 내용들이 생각 외로 많은 인사이트를 얻을 수 있었다.질문하고자 했던 내용들이 다른 분 질문에 있기도 했고 개발 외 질문을 하고 싶어서 우빈님께 개인 취향의 저가 커피 브랜드 1순위와 선호하는 취향에 대해 여쭤보았다.우빈님께서는 저가 커피는 거의 안 드신다고 한다… 그래도 재밌는 영상을 추천해주셨다.항상 카페가면 필터 커피가 있으면 필터 커피를 먹어보거나 그 카페의 스페셜한 원두를 맛을 보려 노력하는데, 항상 먹고 나면 기억이 나지 않는다… ‘이게 어떤 맛이었더라..?’ 하게 된다. 이런 소소한 것에서 취미를 가지는 것도 재밌는 것 같다.코드 리뷰기간 내에 신청해서 우빈님께 코드 리뷰를 받을 수 있었다! 라이브 동안 다른 분 코드 리뷰 해주시는 것도 같이 경청하였는데, 생각보다 내가 놓친 부분들을 여기서 얻을 수도 있었다. 물론 내가 작성한 코드를 리뷰 해주신 것이 직접적으로 바로 와닿았지만, 다른 분 코드 리뷰에서도 배울 점이 있었다.다른 분 코드 리뷰를 해주시면서 퀴즈로 내신 부분이 있다. 퀴즈 내용은 간략하게 설명하면 ‘try-catch에서 예외를 throw했는데 왜 잡히지 않을까?’이다. 코드를 보여주셨을 때 문제가 없어보였다. 그래서 나는 ‘import가 잘못된거 아니야?’라고 생각을 해서 정답을 맞췄다!사실 미션 진행하면서 정말 유사한 문제를 겪은 적이 있었다. 분명 메서드 파라미터가 똑같은데 파라미터가 다르다고 컴파일 에러가 났었다. 미션 특성 상, 맨 처음 리팩토링 이전 패키지와 이후 패키지의 클래스는 똑같다. 그래서 리팩토링 패키지에서 import를 잘못하면, 다른 패키지의 클래스를 가져온다. 그래서 문제가 발생했었는데 이와 똑같은 문제라 운이 좋게도 맞출 수 있었다.영광의 흔적!!코드 리뷰 해주시는 것을 보며 내가 배운 점들은 다음과 같다.Enum의 valueOf 메서드를 통한 생성valueOf 대신 String을 받아서 객체를 생성하는 정적 메서드를 구성사물함 이용 가능 여부를 StudyCafePassType에 저장하는 부분도 좋음get/set은 관용적인 어구로 필드에 대한 getter/setter가 아니더라도, 메서드에 get~~, set~~ 으로 네이밍하는 건 지양하자.early return 보다 else-if가 더 배타적인 내용을 나타내지 않을까?if 절 내에 return을 봐야한다.구조화보다 적절한 책임의 분배가 더 중요하다.위 내용은 다른 분들의 코드 리뷰에서 내가 배운 부분들이다. 다음은 내가 질문하거나 리뷰해주신 내용이다.있을 수도, 없을 수도 있는 필드에 대한 관리를 어떻게 해야할까요?사물함 이용권을 별도 필드로 관리하고 EMPTY 객체를 넣는다? (내용이 정확히 기억이 나지 않는다….)PassReader - FilePassReader로 PassReader의 책임을 부여하고, File에 저장된 Pass를 읽는 구현체로 분리해도 괜찮을까요?괜찮은 접근인 것 같다.Order order = Order.create() 후, order.add(pass) 와 같은 형식으로 주문이후에 주문의 내용이 바뀔 위험성이 존재하므로, 만들어놓고 추가하는 것보다 마지막에 한 번에 Order 객체를 만드는 것이 더 안정적이다. 위험성을 제거하자. 가변보다 불변이 더 좋다!우빈님꼐서 약 1시간 45분동안 라이브를 진행해주셨다. 긴 시간동안 라이브로 진행한다는 것이 쉽지 않으실텐데, 덕분에 나는 유익한 시간을 보낼 수 있었던 것 같다! 직접적으로 우빈님과 소통할 수도 있고 내가 얻을 수 있는 인사이트도 챙겨 소중한 시간이었다. 처음에 말씀하셨던 ‘함께 자라기’를 위해 더 노력해야겠다.

백엔드워밍업스터디발자국클린코드테스트

suover

인프런 워밍업 클럽 스터디 3기 - 백엔드 클린 코드, 테스트 코드 1주차 발자국

Readable Code: 읽기 좋은 코드를 작성하는 사고법Practical Testing: 실용적인 테스트 가이드강의와 함께한 인프런 워밍업 클럽 스터디 3기 - 백엔드 클린 코드, 테스트 코드 (Java, Spring Boot)1주차 발자국 입니다.학습 내용 요약이번 주 동안 [Readable Code: 읽기 좋은 코드를 작성하는 사고법] 강의를 통해 크게 두 가지 주제를 학습했습니다.1⃣ 추상과 구체추상(abstract)은 코드나 로직에서 세부 구현을 생략하고 핵심 의미만 전달하는 방식입니다.구체(concrete)는 코드나 로직의 세부적이고 구체적인 구현을 모두 드러내는 표현 방식입니다.이 개념을 코드에 적용하면 다음과 같은 장점이 있습니다.추상화된 이름 짓기와 메서드 분리를 통해 복잡한 코드를 단순화할 수 있습니다.협업 시 팀원에게 필요한 수준의 정보를 전달할 수 있어 효율적입니다.코드 유지보수가 용이해지고 가독성이 높아집니다.2⃣ 논리와 사고의 흐름, SOLID 원칙📌 논리, 사고의 흐름Early Return: 불필요한 else를 제거하고 조건이 맞지 않을 때 즉시 return해 코드 흐름을 명료하게 유지하는 방법입니다.사고의 Depth 줄이기: 중첩 분기문과 반복문의 깊이를 줄이고, 변수를 필요한 곳 가까이에 선언해 인지적 부담을 낮춥니다.부정어 최소화: 긍정적이고 직관적인 표현으로 메서드 이름을 짓고, 부정 연산자(!) 사용을 줄입니다.📌 SOLID 원칙SRP(단일 책임 원칙): 클래스가 하나의 명확한 책임만 가지도록 설계해야 합니다.OCP(개방-폐쇄 원칙): 기존 코드를 수정하지 않고 확장할 수 있도록 설계합니다.LSP(리스코프 치환 원칙): 자식 클래스가 부모 클래스를 대체해도 프로그램이 정상 작동하도록 설계합니다.ISP(인터페이스 분리 원칙): 사용하지 않는 메서드를 구현하도록 강요받지 않도록 인터페이스를 작게 분리합니다.DIP(의존성 역전 원칙): 고수준 모듈과 저수준 모듈 모두 추상화된 인터페이스에 의존하여 유연성을 높입니다.미션 해결 과정 & 회고Day 2 미션: 추상과 구체 예시미션추상과 구체 강의를 듣고, 생각나는 추상과 구체의 예시 작성해결 과정일상적인 예시를 통해 추상과 구체 개념을 명확히 이해하려고 했습니다.회사 출근이나 배달 음식 주문 등 일상 사례를 통해, 간략히 표현된 추상과 세부적인 표현을 비교하여 정리했습니다.관점 및 이유개념을 일상적이고 친숙한 예시로 접근함으로써 이해를 높이고자 했습니다.추상과 구체의 차이를 분명히 보여줄 수 있는 사례를 선택했습니다.회고일상적 예시 덕분에 개념 이해가 훨씬 쉬워졌고, 추상화의 필요성과 구체적 표현의 중요성을 체감할 수 있었습니다. 앞으로 코드 작성 시 이 관점을 계속 유지할 생각입니다.🔗 Day 2 미션 블로그 링크 Day 4 미션 1: 코드 리팩토링미션중첩 if-else 구조의 주문 검증 메서드 리팩토링접근 관점코드의 인지적 복잡성을 줄이기 위해 Early Return 방식을 사용했습니다.각 조건이 독립적이며, 직관적으로 위에서 아래로 읽히도록 조건 검사를 순서대로 배치했습니다.추가로 주문(Order) 객체가 자기 책임을 다하는 방식으로 책임을 위임하는 방법도 고려했습니다.해결 방법public boolean validateOrder(Order order) { // 주문 항목 유무 if (order.getItems() == null || order.getItems().isEmpty()) { log.info("주문 항목이 없습니다."); return false; } // 총 가격 검증 if (order.getTotalPrice() <= 0) { log.info("올바르지 않은 총 가격입니다."); return false; } // 고객 정보 검증 if (!order.hasCustomerInfo()) { log.info("사용자 정보가 없습니다."); return false; } // 여기까지 왔다면 유효한 주문 return true; }회고Early Return을 적용한 결과, 가독성이 확연히 높아졌고 코드를 읽는 과정이 더 명확해졌습니다. 앞으로도 복잡한 조건 로직을 만날 때 적극적으로 이 방법을 적용할 생각입니다. Day 4 미션 2: SOLID 원칙 정리미션SOLID 원칙을 자신만의 언어로 정리해결 과정각 원칙을 "왜 이런 원칙이 필요한지" 스스로에게 질문하며 이해했습니다.실제 코드 예시를 작성하여, SOLID 원칙을 위반한 코드와 준수한 코드를 비교하면서 학습했습니다.회고원칙을 "내 언어"로 설명하면서, 이전에 막연히 알고 있던 개념들이 훨씬 더 명확하게 다가왔습니다. 단순히 원칙을 외우는 것과 달리, 직접 코드를 작성하고 리팩토링 과정을 거치며 더 깊이 이해할 수 있었습니다.🔗 Day 4 미션 블로그 링크회고이번 주 스터디를 통해 코드를 작성할 때 "추상과 구체의 적정선을 찾는 것이 중요하다"는 점을 깊이 깨달았습니다. 특히 코드가 너무 구체적이면 가독성이 떨어지고, 너무 추상적이면 구체적인 동작이 모호해진다는 점을 직접 실습과 미션을 통해 경험할 수 있었습니다. 또한 Early Return이나 중첩 최소화 등 작은 코드 개선만으로도 가독성이 크게 높아지는 것을 체감할 수 있어 유익했습니다.스스로 칭찬하고 싶은 점미션을 통해 배운 내용을 바로 적용하여 리팩토링하면서 개념을 내 것으로 만들려고 노력한 점이 뿌듯했습니다.SOLID 원칙을 단순히 암기하는 것이 아니라, 스스로 이해하고 예시 코드까지 작성하면서 명확히 내 언어로 표현한 것이 좋았습니다.아쉬웠던 점 & 보완할 점가끔 너무 추상화에 집중하다 보니 코드가 실제 구현에서 너무 멀어지는 경우가 있어, 구체성과 추상성을 더 균형 있게 다루는 연습이 필요하다고 느꼈습니다.SOLID 원칙을 실제 프로젝트 코드에 적용해 보지 못한 점은 조금 아쉬웠습니다. 앞으로 개인 프로젝트에 적용하여 체득할 계획입니다.다음 주 학습 목표다음 주는 강의의 다음 섹션인 "리팩토링" 부분을 학습할 예정입니다. 특히 리팩토링 실습을 통해 SOLID 원칙을 실제 코드에 적용하고, 객체 지향적 사고방식을 내 것으로 만드는 것을 목표로 삼고 있습니다.소감이번 주 강의를 통해 코드 가독성이라는 주제가 얼마나 중요하고 광범위한지 실감했습니다. 앞으로도 이번 학습 내용을 프로젝트에 적극 적용하며, 읽기 좋고 유지보수하기 좋은 코드를 꾸준히 작성하는 습관을 들이겠습니다.감사합니다!

백엔드인프런워밍업클럽스터디백엔드클린테스트코드발자국회고

whffkaos007

워밍업 클럽 2기 BE 클린코드&테스트 - 회고 3회

이 글은 박우빈님의 강의를 참조하여 작성한 글입니다. 벌써 3주차가 되었습니다. 읽기 좋은 코드 관련 강의가 마무리되고 실용적인 테스트 강의를 시작했습니다. 부족한 부분도 있겠지만 열심히 달려온 과정을 적겠습니다. 강의 목적테스트 코드의 중요성과 작성해야 하는 이유테스트 코드를 작성하는 방법  테스트의 필요성테스트는 기능에 대한 부가적인 요소이다. 실제로는 기능을 구현하기 바쁘다. 그런데도 왜 테스트의 중요성이 강조될까?테스트를 작성하면 위와 같은 단점이 있다. 반대로 테스트를 작성하지 않는다면 어떻게 될까? 커버할 수 없는 영역 발생경험과 감에 의존 → 인간이기에 이럼.늦은 피드백 → 수동으로 테스트 해야 함.유지보수 어려움 → 확장, 수정이 일어난다면 어디까지 영향을 미칠지 모름소프트웨어 신뢰성 낮음 → 언제 어디서 버그가 터질지 불안함  테스트 코드를 작성하지 않는다면변화가 생기는 매순간마다 발생할 수 있는 모든 Case를 고려해야 한다.변화가 생기는 매순간마다 모든 팀원이 동일한 고민을 해야 한다.빠르게 변화하는 소프트웨어의 안정성을 보장할 수 없다.테스트 코드가 병목이 된다면프로덕션 코드의 안정성을 제공하기 힘들어진다.테스트 코드 자체가 유지보수하기 어려운, 새로운 짐이 된다.잘못된 검증이 이루어질 가능성이 생긴다. 테스트 코드를 작성하지 않는다면변화가 생기는 매순간마다 발생할 수 있는 모든 Case를 고려해야 한다.변화가 생기는 매순간마다 모든 팀원이 동일한 고민을 해야 한다.빠르게 변화하는 소프트웨어의 안정성을 보장할 수 없다.테스트 코드가 병목이 된다면프로덕션 코드의 안정성을 제공하기 힘들어진다.테스트 코드 자체가 유지보수하기 어려운, 새로운 짐이 된다.잘못된 검증이 이루어질 가능성이 생긴다.   그럼 올바른 테스트 코드를 서비스에 적용시킨다면 어떤 결과를 가져올까?올바른 테스트 코드는자동화 테스트로 비교적 빠른 시간 안에 버그 발견, 수동 테스트에 드는 비용을 크게 절약소프트웨어의 빠른 변화를 지원한다.팀원들의 집단 지성을 팀 차원의 이익으로 승격시킨다.가까이 보면 느리지만, 멀리 보면 가장 빠르다.    테스트 케이스 세분화하기 테스트에는 해피 케이스와 예외 케이스가 있다.   우리는 요구사항대로 기능을 구현할 때, 이외 여러 경우를 고려해야 한다 예를 들어 커피 주문하기에 대한 기능을 구현할 때는 커피의 개수가 1 이상이여야 한다는 숨겨진 조건이 있다.이처럼 숨겨진 조건을 고려하여 작성할 때 도움이 되는 것은 경계값 테스트이다. 경계값 : 범위(이상, 이하, 초과, 미만), 구간, 날짜 등경계값을 기준으로 테스트를 고려해야 기능(도메인)에 대한 명확한 인지에 도움이 되며 예외 상황도 쉽게 파악할 수 있다.   테스트하기 어려운 영역을 분리하기테스트 코드는 작성하기 쉬운 부분과 어려운 부분이 존재한다. 예를 들어 오전 10시부터 오후 2시까지만 주문이 가능한 조건이 있다 가정하자. order(List<Item> items){ LocalDateTime now = LocalDateTime.now(); if(now < 오전 10시 || 오후 2시 < now){ // 예외 발생 } ... } 위 메서드에 대한 테스트를 진행하면 어떻게 될까?테스트를 실행하는 시간에 따라 성공 여부가 달라질 것이다.그렇다면 이처럼 테스트하기 어려운 상황이 생기면 어떻게 해야 할까?바로 테스트하기 어려운 부분을 외부로 분리해야 한다.  order(List<Item> items, LocalDateTime time){ if(time< 오전 10시 || 오후 2시 < time){ // 예외 발생 } ... } 위 코드처럼 시간이란 테스트하기 어려운 영역을 외부로 분리하여 파라미터로 받는다. 이렇게 코드를 작성하면 테스트하는 시간마다 성공 여부가 달라지지 않는다.테스트라는 기능을 온전히 수행할 수 있을 것이다. 이처럼 우리는 테스트하기 어려운 부분이 있다면 이를 외부 세계로 분리하여 테스트하기 쉽게 만들어야 한다.그럼 이런 어려운 부분은 무엇이 있을까? 어려운 영역관측할 때마다 다른 값에 의존하는 코드현재 날짜/시간, 랜덤 값, 전역 변수/함수, 사용자 입력 등외부 세계에 영향을 주는 코드표준 출력, 메시지 발송, 데이터베이스에 기록하기 등쉬운 영역 → 순수함수(pure fuction)같은 입력에는 항상 같은 결과외부 세상과 단절된 형태테스트하기 쉬운 코드이러한 영역은 위와 같이 있지만 직접 경험하면서 어떤 부분을 분리하는 것이 더 좋을 지에 대한 고민을 해야 시야를 기를 수 있다. TDD:Test Driven Development TDD는 위 구조를 통해 테스트 코드를 작성하는 것이다. 구현 → 테스트 순이 아닌테스트 → 구현 순으로 진행하는 방법이다.왜 TDD가 좋을까? 어떤 가치를 지니지?가장 큰 가치 중 하나는 빠른 피드백이다. 선 기능 구현, 후 테스트 작성테스트 자체의 누락 가능성특정 테스트 케이스만 검증할 가능성(해피 케이스)잘못된 구현을 다소 늦게 발견할 가능성테스트를 먼저 작성한다면 테스트에 대한 고려에 대한 시야를 가질 수 있다. 선 테스트 작성, 후 기능 구현복잡도 낮은 테스트 가능한 코드로 구현할 수 있게 한다.유연하며 유지보수가 쉽다.쉽게 발견하기 어려운 엣지 케이스를 놓치지 않게 해준다.구현에 대한 빠른 피드백을 받을 수 있다.과감한 리팩토링이 가능하다.  TDD : 관점의 변화지금까지 기능 구현과 테스트의 관계를 봤을 때, 우리는 아래와 같이 기능과 테스트가 상호작용하는 구조를 추구해야 한다.기존에는 테스트는 구현부의 검증을 위한 보조 수단이었다면 TDD를 이용하면 테스트와 상호 작용하며 발전하는 구현부를 가질 수 있다.클라이언트 관점에서의 피드백을 주는 Test Driven.  테스트는 문서다. 문서란?프로덕션 기능을 설명하는 테스트 코드 문서다양한 테스트 케이스를 통해 프로덕션 코드를 이해하는 시각과 관점을 보완어느 한 사람이 과거에 경험했던 고민의 결과물을 팀 차원으로 승격시켜서, 모두의 자산으로 공유할 수 있다.우리는 테스트 코드를 통해 해당 기능이 어떻게 동작하는 지 파악할 수 있다. 해피 케이스와 예외 케이스를 통해 코드(도메인 지식)를 인지하는데 도움을 얻을 수 있고 기존의 테스트를 통해 잘못된 부분이나 놓친 부분을 파악할 수 있다.우리는 항상 팀으로 일한다. 팀원에게 어떻게 보일 지 항상 고민하면서 작성하는 습관을 기르자.DisplayName을 섬세하게@DisplayName("음료 1개 추가 테스트") // 1 @DisplayName("음료 1개를 추가하면 주문 목록에 담긴다.") // 2 우리는 신규 입사자다. 서비스의 테스트 코드를 봤을 때 1, 2 중 어떤 것이 테스트 코드의 담긴 의미를 더 많이 전달해주는가?바로 2번이다. 왤까? 명사의 나열보다 문장으로 표현하기우리는 좀 더 명확한 의미를 파악할 수 있다. 정보의 생략의 없기 때문이다.테스트 행위에 대한 결과까지 기술하기이 또한 해당 기능에 대한 모든 정보를 알기 위해 행위에 대한 결과까지 기술하자.도메인 용어를 사용하여 한층 추상화된 내용을 담기(메서드 자체의 관점보다 도메인 정책 관점으로)테스트의 현상을 중점으로 기술하지 말 것  특정 시간 이전에 주문을 생성하면 실패한다. // 1 영업 시작 시간 이전에 주문을 생성하면 생성할 수 없다. // 21, 2 중 당연히 2가 더 추상화된 내용을 전달해준다. 우리가 도메인 지식을 이해하는데 더욱 도움을 준다.또한 ‘실패한다’와 ‘생성할 수 없다’를 보자. 실패한다는 것은 도메인에 대한 정보가 아니다. 단순히 테스트의 성공, 실패라는 결과에 의존한 것이다. 우리는 도메인의 정보를 전달할 수 있게 주의해야 한다.  BDD 스타일로 작성하기given - when - then → ‘테스트의 준비 - 행위 - 결과’ 를 의미한다.명확하게 표시해 줌으로써 테스트 코드를 좀 더 이해하기 쉽다.  Test의 양면성과 바라봐야 할 시각(개인 정리)이처럼 테스트에 대한 이점과 작성법이 있다. 하지만 테스트 또한 비용이다. 테스트가 오히려 기능 구현보다 비용이 비쌀 수도 있고 그렇기에 이를 단순 테스트의 용도로 바라보거나 기능 구현에 편향된 모습을 보이며 테스트 작성이 오히려 불필요하다는 반론도 많다. 하지만 우리는 지금 당장이 중요한 것이 아니다. 서비스가 운영된다면 종료되기 전까지 성장할 것이다. 또한 사용자가 많아지고 서비스의 규모가 커질수록 우리 코드는 더 많은 상호 협력을 요구하기에 결합도가 커지기 마련이다.이에 따라 장애가 발생할 확률도 높다. 테스트를 작성할 때, 항상 고민하는 습관이 있어야 장애의 발생을 예방할 수 있다. 테스트를 단순 검증의 역할로만 바라보지 말고 문서의 역할까지 크게 바라보자. 그럼에도 비용은 무시할 수 없다. 비용과 비용에서 오는 이점을 고려하여 우리는 테스트를 다루는 것도 필요한 요소라고 생각한다. 

백엔드테스트테스트필요성테스트작성법

유진

인프런 워밍업 클럽 BE 2기 - 클린코드 / 테스트코드 발자국 4주차

강의 출처 Practical Testing: 실용적인 테스트 가이드 학습 내용레이어드 아키텍쳐(Layered Architecture)와 테스트 - 노션 정리Test DoubleDummy - 아무것도 하지 않는 깡통 객체.Fake - 단순한 형태로 동일한 기능은 수행하나, 프로덕션 초기에는 부족한 객체.Stub - 테스트에서 요청한 것에 대해 미리 준비한 결과를 제공하는 객체. 그 외에는 응답하지 않는다.Spy - stub이면서 호출된 내용을 기록하여 보여줄 수 있는 객체. 일부는 실제 객체처럼 동작하고 일부만 stubbing 할 수 있다.Mock - 행위에 대한 기대를 명세하고, 그에 따라 동작하도록 만들어진 객체.Stub / Mock 의 차이? -> Stub은 상태 검증 / Mock은 행위 검증@Mock, @Spy, @MockBean, @SpyBean, @InjectMocks - 노션 정리BDDMockito - Mockito를 BDD 스타일로 wrap해서 사용.// given Mockito.when(mailSendClient.sendEmail(anyString(), anyString(), anyString(), anyString())) .thenReturn(true); // BDDMockito given BDDMockito.given(mailSendClient.sendEmail(anyString(), anyString(), anyString(), anyString())) .willReturn(true);Classicist VS. Mockist진짜 객체로 테스트를 하고 필요한 경우에만 mocking해서 테스트를 하자. (Classicist)외부 시스템의 경우 mocking 처리해서 테스트.하나의 테스트는 하나의 주제만을 가져야 한다. 논리구조(if문, for문..) 같은 경우는 지양하는 것이 좋다. 만약 케이스 확장이 필요하다면 @ParameterizedTest 사용. 테스트 환경의 독립성 보장 - given절에서는 값 넣어줄 때 생성자, builder 사용.테스트 간 독립성 보장 - 두 가지 이상의 테스트가 하나의 자원을 공유할 때 주의해야 한다. 공유 자원(인스턴스)의 여러 시나리오를 테스트 하고 싶을 경우? -> @DynamicTest 사용.Test Fixture각각의 테스트에서 given이 중복되는 경우 @BeforeEach 에 작성할 경우 주의 해야 할 점 -> 각 테스트 입장에서 알지 못해도 테스트 내용을 이해하는데 문제가 없는지 / 수정해도 모든 테스트에 영향을 주지 않는지 고려해야 한다.data.sql 사용 지양. -> 무엇을 테스트하고 있는지 파악하기 어려울 수 있다.테스트 클래스마다 builder를 만들어서 각자 필요한 파라미터만 사용한다. (builder class를 만들어서 한 곳에서 관리하는 것이 오히려 복잡도를 늘어나게 한다.)Test Fixture 클렌징deleteAll()mapping된 테이블을 조회 후 delete 한다. 연관된 테이블을 모두 조회한 후 삭제하기 때문에 시간과 비용이 들 수 있다.테이블의 삭제 순서를 고려해야 될 수 있다.deleteAllInBatch()테이블의 삭제 순서를 고려해야 한다.여러 조건들(외래키 조건..)에 따라 삭제가 되지 않을 수 있다.테스트 환경 통합하기ServiceTest, RepositoryTest@ActiveProfiles("test") @SpringBootTest public abstract class IntegrationTestSupport { @MockBean protected MailSendClient mailSendClient; }ControllerTest@WebMvcTest(controllers = { OrderController.class, ProductController.class }) public abstract class ControllerTestSupport { @Autowired protected MockMvc mockMvc; @Autowired protected ObjectMapper objectMapper; @MockBean protected OrderService orderService; @MockBean protected ProductService productService; }private 메서드의 테스트는 하지 말아야 한다. 꼭 해야 된다면 따로 객체를 분리해서 테스트를 할 수 있다.테스트에서만 필요한 메서드는 만들어도 되지만, 보수적으로 접근해야 한다. 미션미션 1 - Layered Architecture 구조의 레이어별 특징과 테스트 작성법 -> 노션 정리미션 2 - @Mock, @Spy, @MockBean, @SpyBean, @InjectMocks 차이점 / @BeforeEach 배치 -> 노션 정리회고인프런 워밍업 클럽 BE 2기 4주차라니 시간이 어떻게 흘러간지 모르겠다. 이번 주는 특히나 내가 평소 궁금했던 것들에 대한 강의여서 더 집중해서 들었던 것 같다. 바로 프로젝트에 적용 해볼 만 한 내용이 많아서 만족스러웠다. 워밍업 클럽 진도를 따라가는게 쉽지는 않았지만 지나고 보니 포기하지 않고 어떻게든 하려고 노력 했던 것이 많은 도움이 된 것 같다.

백엔드인프런워밍업클럽2기테스트

김진수

인프런 워밍업 클럽 2기 백엔드 - 발자국 4주차

인프런 워밍업 클럽 2기 발자국 4주차1. 한 주의 정리드디어 마지막 주차이다. 이번 주차는 Layerd Architecture에서 Presentation Layer와Test Double 그리고 테스트를 작성하기 위한 팁들, 마지막으로 부록이 있다. 마지막 페이즈답게 테스트를 위한 지식공유자님의 개인적인 견해와 팁들이 많이 녹아져 있는 강의들이라 좋았다. ✅ 섹션 6. Presentation Layer Test표현 계층에 대한 테스트를 작성하는 방법에 대해 배움,중요한 것은 비즈니스 로직이 들어가는 것이 아니라 넘겨온 값들에 대한 검증이라고 생각한다고 하더라,나 또한 동의한다. ✅ 섹션 7. Mock을 대하는 자세Test Double에 대해 배우고 각 객체의 사용법 그리고 지식공유자님의 개인적 견해가 들어가 있다. 처음으론 Stub과 Mock! 많이 헷갈렸는데Stub은 상태 검증Mock은 행위 검증이라는 관점에서 접근하는 방법을 배웠다. 다음은 Mock하면 항상 나오는 말인데, Classicist VS Mockist이다. 우선 나는 Classicist쪽에 더 가깝다. Mocking을 하면 항상 통과할텐데..(왜냐면 그렇게 짜여지니까)실제상황에서의 다양한 변인들을 통제할 수 있을까? 하는 생각이다. 그렇지만 완고한 고전파는 아니고 Mocking하고 넘어가도 되는 부분은 충분히 그럴 수 있다는 생각이다. ✅ 섹션 8. 더 나은 테스트를 작성하기 위한 구체적 조언완전 농축 액기스 섹션이다.지식공유자님께서 여지껏 겪어오면서 고민하고, 또 좋았던 경험을 바탕으로 팁들을 녹여놓은 섹션이다.다른 섹션들도 물론 군데군데 그것들이 녹여져있지만, 이 섹션에서는 아예 그냥 키워드를 퍼먹여준다.꼭꼭 씹고 내 것으로 만들자. ✅ 섹션 9. Appendix부록인데 부록이 아니다.우선 처음은 테스트 코드를 조금 친숙히 다가가는 법, 아닌 말로 뭐 만드는게 없는데 테스트코드를 어떻게 짜요? 란 생각이 은연 중에 있었다.그런 나의 썩은 정신 상태를 고쳐줄 수 있는 좋은 방법이었다. Spring REST Docs는 확실히 Swagger와는 다른 장점을 보인다.나는 Docs쪽을 더 선호하는 데 이 강의에서 말한 Swagger의 단점이 나는 아주 치명적으로 느껴져서이다. ✅ 섹션 10. Outro그 동안 학습했던 것들에 대한 정리와 조언..? 사람들은 매번 겪어야 깨닫는 특성이 있는데.나 또한 마찬가지고, 어찌됐든 테스트를 짜는게 느린게 아니라 빠른거다 라는 것은 점점 피부로 느껴진다. 흔히 나는 게임에 비유를 하는데 테스트 코드는 공략이다. 이 요구사항을 어떻게 해치울 지 공략이 있다면, 프로덕션 코드는 공략대로 가면 된다. 근데 공략이 없다면.. 좀 더 오래걸리더라 ..  2. 미션이번 주차에서는 Layerd Architecture 구조의 레이어 별 특징과 어떻게 테스트하면 좋을 지 그리고 @Mock, @MockBean, @Spy, @SpyBean, @InjectMock에 대한 정리와예시 테스트의 시나리오 재배치 2가지의 미션이 있었다. 하나하나 작성하기 양이 좀 되어 따로 블로그에 작성한 링크를 참조하려 한다. ✅ 미션 Day-15https://romanc3.tistory.com/131 ✅ 미션 Day-18https://romanc3.tistory.com/132 🤔 내 생각을 정리하자면강의도 좋았지만, 워밍업 클럽 자체가 좋은 기획 같다.동일한 관심사를 가진 사람들과 같은 강의를 보며 다른 생각을 경험할 수 있다는 점이 아주 좋았고,지식 공유자님과 면대면은 아니더라도 의견을 주고 받을 수 있는 공간을 제공 받을 수 있다는 것이 큰 이득으로 다가왔다. 잘하고 싶다면, 잘하는 사람을 모방 하라는 말이 있다.모방을 우습게 보는데.. 따라하기란 쉬운게 아니다. 결국 똑같이 따라할 수 있다는 것은 똑같은 실력이란 것이다.그러한 관점에서 나는 내가 생각하기에 좋은 개발자 분들을 많이 알 수 있게 된 기회였다. 언제까지나 테스트에 매몰되어 있지는 말고, 얼른 마무리해서 습관적으로 테스트를 작성하고 또 다른 부분을 신경쓸 수 있는 개발자로 거듭나길 연습해야겠다 😃

백엔드백엔드테스트

하양이

워밍업 클럽 2기 BE 클린코드&테스트 발자국 4주차

Day 15. Spring & JPA 기반 테스트: Presentation LayerPresentation Layer외부 세계의 요청을 가장 먼저 받는 계층파라미터에 대한 최소한의 검증을 수행한다.MockMvcMock(가짜) 객체를 사용해 스프링 MVC 동작을 재현할 수 있는 테스트 프레임워크미션 - 생각하는 레이어별 특징과 테스트 방법Persistence Layer데이터 저장 및 조회를 담당데이터베이스에 연결하여 데이터 저장 및 조회가 잘 되는지 테스트Business Layer비즈니스 로직을 담당비즈니스 규칙에 맞게 올바르게 동작하는지 테스트Presentation Layer사용자와 상호작용을 담당사용자가 보낸 요청의 유효성 체크와 응답이 제대로 가는지 테스트Day 16. Mock을 마주하는 자세Test Double상태 검증 -> Stub행위 검증 -> Mock@Mock, @Spy, @InjectMocks@Mock : Mock 객체로 만듬@InjectMocks : Mock 객체를 주입해줌@Spy : 일부만 Mock 객체로 만듬BDDMockitoMockito와 동일하다.BDD 스타일로 이름만 변환.Mockito.when().thenReturn(); BDDMockito.given().willReturn();Day 17. 더 나은 테스트를 작성하기 위한 구체적 조언한 문단에 한 주제!: 하나의 테스트 코드에서 하나의 테스트만!완벽하게 제어하기: 제어 불가능한 영역은 상위 계층으로!테스트 환경의 독립성을 보장하자테스트 간 독립성을 보장하자한 눈에 들어오는 Test Fixture 구성하기: given 절을 구성할 때의 주의사항Test Fixture 클렌징테스트 수행도 비용이다. 환경 통합하기Q. private 메서드의 테스트는 어떻게 하나요?해서도 안 되고 할 필요가 없다.Q. 테스트에서만 필요한 메서드가 생겼는데 프로덕션 코드에서는 필요 없다면?만들어도 되지만 보수적으로 접근하기!Day 18. 학습 테스트 | REST Docs학습 테스트잘 모르는 기능, 라이브러리, 프레임워크를 학습하기 위해 작성하는 테스트Spring REST Docs테스트 코드를 통한 API 문서 자동화 도구Spring REST Docs : https://docs.spring.io/spring-restdocs/docs/current/reference/htmlsingle/Asciidoctor : https://asciidoctor.org/Swagger : https://swagger.io/미션 - @Mock, @MockBean, @Spy, @InjectMocks의 차이 정리@Mock: 해당 객체를 Mock 객체로 만든다.@MockBean: Spring의 ApplicationContext에 Mock 객체로 만들어 빈으로 등록한다.@Spy: 일부분만 Mock 객체로 만들어준다.@InjectMockes: Mock 객체로 만들어 주입해준다.미션 - 각 항목을 @BeforeEach, given절, when절 배치@BeforeEach void setUp() { 1-1., 2-1., 3-1. 사용자 생성에 필요한 내용 준비 1-2., 2-2., 3-2. 사용자 생성 } @DisplayName("사용자가 댓글을 작성할 수 있다.") @Test void writeComment() { // given 1-3. 게시물 생성에 필요한 내용 준비 1-4. 게시물 생성 1-5. 댓글 생성에 필요한 내용 준비 // when 1-6. 댓글 생성 // then 검증 } @DisplayName("사용자가 댓글을 수정할 수 있다.") @Test void updateComment() { // given 2-3. 게시물 생성에 필요한 내용 준비 2-4. 게시물 생성 2-5. 댓글 생성에 필요한 내용 준비 2-6. 댓글 생성 // when 2-7. 댓글 수정 // then 검증 } @DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.") @Test void cannotUpdateCommentWhenUserIsNotWriter() { // given 3-3. 사용자2 생성에 필요한 내용 준비 3-4. 사용자2 생성 3-5. 사용자1의 게시물 생성에 필요한 내용 준비 3-6. 사용자1의 게시물 생성 3-7. 사용자1의 댓글 생성에 필요한 내용 준비 3-8. 사용자1의 댓글 생성 // when 3-9. 사용자2가 사용자1의 댓글 수정 시도 // then 검증 }회고아기다리 고기다리던 금요일 특강을 회사일때문에 참가하지 못했다.하필 저번주도 다음주도 아닌 이번주 금요일에 하필...아쉽지만 지금까지 공부하고 배운 내용을 내것으로 만들어 가야겠다.출처https://inf.run/zgJk5https://inf.run/kHiWM

백엔드워밍업클럽테스트

고리오영감

[인프런 워밍업 클럽 스터디 2기 백엔드] 4주차 발자국

개요4주차 종료!Practical Testing 실용적인 테스팅 가이드를 완강 했다!인프런 워밍업 클럽 스터디 2기 백엔드 과정... 이것으로 끝!감격스럽다.4주차 공부한 내용권장 진도표에 맞게 진도를 나갔네? 배운 것들1. MockitoMockito 라이브러리를 써봤지만, 사용할 때마다 찝찝한 느낌을 갖고 사용해왔다.이번 주차에 강의를 학습하며 Mockito를 언제 쓰면 좋은지 @MockBean 과 @Mock 의 차이는 무엇인지를 제대로 알 수 있었다. 2. Classicist vs Mockist우빈님은 Classicist 인데, 강의를 들어보니 나도 Classicist가 되었다.정답이 있는 문제는 아니라 상황에 맞게 적절히 Mocking 할 땐 하고, 통합 테스트할 땐 하고 하면 될 듯 하다. 3. Test Fixture 가이드라인다른 강의에서 Fixture를 하나의 클래스로 관리하라고 배웠었다.그래서 그렇게 사용해왔는데, 문제는 원하는 필드를 지정해주고 싶을 때마다 Fixture를 생성하는 메서드가 증가하게 되고,메서드가 증가하면 관리가 너무 어려워지곤 했다. 더군다나 큰 프로젝트 같은 경우 엔티티당 필드가 수 십개가 넘어가는 게 대부분 인데, 이걸 전부 하나의 클래스로 관리하는 건 별로 좋은 방법 같지는 않다.  강의를 통해서 Test Fixture를 어떻게 생성하는 지, 어떻게 생성 메서드를 관리하는 지 배울 수 있었다.  4. 테스트 환경 통합하기테스트 환경을 통합해야 한다는 개념 자체를 몰랐다. 이런 게 가능할 거라 생각도 못했던 것 같다. 강의 들으면서 조금 충격을 받았다. 5. private 메서드를 테스트하고 싶은가? 그러면 책임 분리를 고민해봐라이것도 충격! private 메서드를 테스트하지 말라고 하시는 말씀에"그렇구나, 테스트 안 해도 되네? 개꿀!" 이렇게 생각하며 설렁설렁 듣고 있었는데,private 메서드를 새로운 객체에 public 메서드로 빼놓고 객체간 협력하게 하는 코드를 작성하시는 모습이 인상적이었다.테스트 생각을 많이 하다 보면 자연스레 객체지향에 가까워질 수도 있음을 깨달았다. 6. 학습테스트프로젝트에 쓰고 싶은 새로운 라이브러리를 테스트로 학습하는 건 정말 좋은 방법인 것 같다. 7. Spring REST docsSwagger만 사용해봤는데 Spring REST docs는 들어는 봤지, 이번에 처음 사용해봤다.설정하는 게 어렵긴 한데, 실무에서 많이 쓰인다고 하니 이번에 경험해볼 수 있어서 좋았다. 미션이번 주차에는 Day 15. 미션, Day 18. 미션이 있었다. Day 15. 미션 - Layered Test 작성법 자기만의 언어로 정리하기배운 내용을 다시 한 번 정리하는 거라 어렵진 않았다.https://zircon-neptune-a7d.notion.site/Day-15-Layered-test-124ba1f1340980ddb599f68747a2ddfe?pvs=4 Day 18. 미션 Day 18. 미션 - 1. @Mock, @MockBean, @Spy, @SpyBean, @InjectMocks의 차이 정리하기역시 배운 내용을 다시 한 번 정리하는 거라 어렵진 않았다.https://zircon-neptune-a7d.notion.site/Day-18-129ba1f134098015983ade0586420ed1?pvs=4 Day 18. 미션 - 2. 수도 코드로 작성된 테스트 3개의 코드를 재배치하기제시된 수도 코드@BeforeEach void setUp() { ❓ } @DisplayName("사용자가 댓글을 작성할 수 있다.") @Test void writeComment() { 1-1. 사용자 생성에 필요한 내용 준비 1-2. 사용자 생성 1-3. 게시물 생성에 필요한 내용 준비 1-4. 게시물 생성 1-5. 댓글 생성에 필요한 내용 준비 1-6. 댓글 생성 // given ❓ // when ❓ // then 검증 } @DisplayName("사용자가 댓글을 수정할 수 있다.") @Test void updateComment() { 2-1. 사용자 생성에 필요한 내용 준비 2-2. 사용자 생성 2-3. 게시물 생성에 필요한 내용 준비 2-4. 게시물 생성 2-5. 댓글 생성에 필요한 내용 준비 2-6. 댓글 생성 2-7. 댓글 수정 // given ❓ // when ❓ // then 검증 } @DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.") @Test void cannotUpdateCommentWhenUserIsNotWriter() { 3-1. 사용자1 생성에 필요한 내용 준비 3-2. 사용자1 생성 3-3. 사용자2 생성에 필요한 내용 준비 3-4. 사용자2 생성 3-5. 사용자1의 게시물 생성에 필요한 내용 준비 3-6. 사용자1의 게시물 생성 3-7. 사용자1의 댓글 생성에 필요한 내용 준비 3-8. 사용자1의 댓글 생성 3-9. 사용자2가 사용자1의 댓글 수정 시도 // given ❓ // when ❓ // then 검증 }제시된 수도 코드는 중복이 많아 보인다. 깔끔하게 정리해보는 미션이었다.내가 재배치한 결과는 아래와 같다.@BeforeEach void setUp() { 1-1. 사용자 생성에 필요한 내용 준비 1-2. 사용자 생성 1-3. 게시물 생성에 필요한 내용 준비 1-4. 게시물 생성 } @DisplayName("사용자가 댓글을 작성할 수 있다.") @Test void writeComment() { // given 1-5. 댓글 생성에 필요한 내용 준비 // when 1-6. 댓글 생성 // then 검증 } @DisplayName("사용자가 댓글을 수정할 수 있다.") @Test void updateComment() { // given 2-5. 댓글 생성에 필요한 내용 준비 2-6. 댓글 생성 // when 2-7. 댓글 수정 // then 검증 } @DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.") @Test void cannotUpdateCommentWhenUserIsNotWriter() { // given 3-3. 사용자2 생성에 필요한 내용 준비 3-4. 사용자2 생성 3-7. 사용자1의 댓글 생성에 필요한 내용 준비 3-8. 사용자1의 댓글 생성 // when & then --> 예외 발생 3-9. 사용자2가 사용자1의 댓글 수정 시도 } 끝! 4주간 알차게 학습했다.미션과 발자국, 두 개 강의 모두 100% 학습해서 뿌듯하다.인프런 워밍업 클럽 스터디 GOOD!  

백엔드테스트Junit5MockitoSpringRestDocs

wisehero

[인프런-워밍업클럽 BE 2기] Day18 과제

1. @Mock, @MockBean, @Spy, @SpyBean, @InjectMocks 의 차이를 한번 정리해 봅시다. 모두 JUnit, Spring 테스트 환경에서 사용된다. 각각의 어노테이션들은 그 목적과 사용 방식이 조금은 다르다. @Mock가짜 클래스 객체를 생성한다. 이 어노테이션에 의해 모킹된 객체는 실제 객체의 메소드 호출에 대해 빈 응답을 준다.외부 의존성을 최소화 해줄 수 있어서 단위 테스트를 작성할 때 유용하다. 실제 객체의 메소드를 호출하지 않으니당연히 실체 객체를 생성하는 것도 아니다. @MockBeanSpringBoot에서 제공하는 어노테이션이고 Spring 애플리케이션의 컨텍스트에서 관리하는 빈을 모킹한다.@Mock은 스프링 컨텍스트와 관련이 없지만 @MockBean은 스프링 컨텍스트와 관련이 있다. 특정 빈을 모킹된 객체로대체할 때 사용된다. 주로 통합 테스트에서 다른 빈과의 의존성을 모킹할 때 유용하다. @Spy스파이 객체는 실제 객체의 메소드 호출을 그대로 유지한다. 특정 메소드를 모킹할 수 있는데 일부 메소드는 실제로 호출되게하고일부 메소드는 모킹할 수 있다.  @SpyBean@MockBean과 마찬가지로 스프링 애플리케이션 컨텍스트에서 관리하는 빈을 스파이한다. @InjectMocksInject라는 말에서 알 수 있듯이 의존성 주입을 도와주는 어노테이션이다. @Mock이나 @Spy를 명시한 클래스들을@InjectMocks라는 어노테이션이 달린 대상 클래스에 의존성을 주입해준다. 주로 단위테스트에서 사용된다.  "@BeforeEach void setUp() { 1-1. 사용자1 생성에 필요한 내용 준비 1-2. 사용자1 생성 3-5. 사용자1의 게시물 생성에 필요한 내용 준비 3-6. 사용자1의 게시물 생성 } @DisplayName(""사용자가 댓글을 작성할 수 있다."") @Test void writeComment() { // given 1-5. 댓글 생성에 필요한 내용 준비 // when 1-6. 댓글 생성 // then 검증 } @DisplayName(""사용자가 댓글을 수정할 수 있다."") @Test void updateComment() { // given 2-5. 댓글 생성에 필요한 내용 준비 2-6. 댓글 생성 // when 2-7. 댓글 수정 // then 검증 } @DisplayName(""자신이 작성한 댓글이 아니면 수정할 수 없다."") @Test void cannotUpdateCommentWhenUserIsNotWriter() { // given 3-3. 사용자2 생성에 필요한 내용 준비 3-4. 사용자2 생성 3-7. 사용자1의 댓글 생성에 필요한 내용 준비 3-8. 사용자1의 댓글 생성 // when 3-9. 사용자2가 사용자1의 댓글 수정 시도 // then 검증 }" 

백엔드백엔드워밍업클럽클린코드테스트

sdsd988

Layered Architecture 구조의 테스트 작성 방법

인프런 워밍업 클럽 백엔드 2기 클린코드, 테스트 코드 참여 미션 수행 중 하나입니다!관련 강의 :Practical Testing: 실용적인 테스트 가이드( https://www.inflearn.com/course/practical-testing-%EC%8B%A4%EC%9A%A9%EC%A0%81%EC%9D%B8-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%80%EC%9D%B4%EB%93%9C/dashboard)1. Layered Architecture ? 소프트웨어 개발 방법 중 하나로, 계층(Layer) 의 수에 따라 N-tier Architecture 부르기도 한다.계층은 어떻게 나누는 것인가? - 책임에 따라서 나눈다.강의에서는 3-tier Architecture 기준으로 나누었다. (Persistence, Business, Prsentation) 2. Layer(Persistence, Business, Prsentation) 1. Persistence Layer  책임 : 도메인(객체)의 생성과 검증 도메인의 생성과 관련된 책임을 가진 계층이다.애플리케이션에서 생성한 객체(Domain)이 데이터베이스에 올바르게 저장되는지 검증하는 과정이 필요하다. Persistent Layer 는 객체가 어떻게 저장되고, 어떤 파라미터를 필요로하는 지 이해할 수 있는 계층이다.테스트 코드 작성을 통해, Business 계층에서 사용될 객체들에 대해 이해를 가질 수 있는 계층이라 생각한다.뒤에 올 Business, Presentation 계층의 작성을 위한 기본적인 토대를 제공하는 계층이다.따라서, 이어질 계층에서 도메인에 대한 고민을 할 필요 없도록 테스트 코드를 작성해주는 것이 필요하다고 생각한다. 2. Business Layer 책임 : 도메인의 비지니스 로직 수행 객체의 생성과 검증이 완료된 토대에서, 서비스의 로직 을 수행하는 책임을 가진 계층이다.Persistent Layer에서 검증되고, 생성된 객체가 수행하는 책임을 검증하는 계층이라고 볼 수 있다.그렇기에, 코드의 양이 많고 테스트 코드 작성도 복잡해진다.Service(Business)은 Persistent(Repository) 의존한다. 즉 2개의 계층이 테스트에 필요해진다.중요하다고 생각하는 지점은, 이 계층은 비지니스 로직 에 집중해야 하는 계층이라는 점이다.객체의 생성과 관련된 코드는 감추고 , 로직과 관련된 코드는 표현되어야 한다.강의를 들으면서, 책임을 어떻게 분리할 것인가? 라는 의문이 생길 수 있다고 생각했다.비지니스 계층을 작성하면서, 도메인과 비지니스 그리고 로직과 로직 사이의 리팩토링이 많이 이루어 질 수 있다고 생각!  3. Presentation Layer책임 : 생성과 로직이 처리된 데이터의 출력 2 계층에서 생성되고, 처리된 데이터가 올바르게 생성되거나 프런트엔드에 올바르게 반환되는지 확인하는 책임을 가진 계층Business 계층에 의존한다. Business Layer 테스트에서 주의해야 할 점과 같이, Presentation Layer 검증에 집중해야 한다.하지만, Business Layer의 특징은 복잡하다는 것이 있었다. 대체(Mock)개념의 필요성Presentation Layer 계층의 테스트에 집중하기 위해 Mockito 를 활용하여, 가짜 객체를 생성하고 테스트에 도입한다.결국 데이터의 바인딩(Binding), 맵핑(Mapping) 에 집중해야 한다.API를 호출하면, 의도된 매개 변수를 받고 올바른 응답을 생성하는 과정을 검증할 수 있어야 한다.  

백엔드백엔드인프런워밍업클럽테스트스프링레이어드아키텍쳐

whffkaos007

워밍업 클럽 2기 BE 클린코드&테스트 : 미션 - Day 15

 이 글은 박우빈님의 강의를 참조하여 작성한 글입니다. 미션 - Day 15 미션 내용Layered Architecture 구조의 레이어별 테스트 작성법을 알아보았습니다. 레이어별로 1) 어떤 특징이 있고, 2) 어떻게 테스트를 하면 좋을지, 자기만의 언어로 다시 한번 정리해 볼까요? 1. 계층별 특칭 Layered Architecture : Presentation Layer <-> Business Layer <-> Persistence Layer Layered Architecture 는 3가지 계층으로 구성된다.웹 클라이언트와 연결된 Controller 부분에 해당하는 Presentation Layer, 서비스의 비즈니스 로직을 처리하는 Business Layer, DAO에 해당하는 Persistence Layer가 있다. 이렇게 계층을 나누는 이유는 관심사의 분리이다. 관심사, 책임을 나누기에 테스트의 작성을 편리하게 하고 신뢰성을 높이고 유지보수 또한 수월하다.  Presentation Layer외부 세계의 요청을 가장 먼저 받는 계층파라미터에 대한 최소한의 검증을 수행한다. Business Layer비즈니스 로직을 구현하는 역할Persistence Layer와의 상호작용(Data를 읽고 쓰는 행위)을 통해 비즈니스 로직을 전개시킨다.트랜잭션을 보장해야 한다. Persistence LayerData Access의 역할비즈니스 가공 로직이 포함되어서는 안 된다. Data에 대한 CRUD에만 집중한 레이어   2. 어떻게 테스트를 하면 좋을지, 자기만의 언어로 다시 한번 정리해 볼까요? 하나의 모듈을 기준으로 독립적으로 진행되는 단위 테스트와 둘 이상의 여려 모듈이 협력하여 기능을 통합적으로 검증하는 통합 테스트가 있다.각 계층에 대한 테스트는 다음과 같이 진행하면 좋을 거 같다. Persistance Layer -> Spring과 통합한 단독 계층 테스트(Spring, Jpa 등 활용한 DB 접근), 단위 테스트 성격 Business Layer -> Persistence를 포함한 통합 테스트 Presentation Layer -> 이외 계층을 Mocking한 단독 계층 테스트, 단위 테스트 성격 강사님의 의견과 유사한 생각을 가지고 있다. 위와 같은 생각은 다음과 같은 이유 때문이다. 이유Persistence Layer는 DAO 관련 계층이다. 컨트롤러나 서비스 등 이외 계층과 협력할 필요가 없다. 그렇기에 스프링, JPA를 활용한 테스트를 진행할 수 밖에 없다. A와 B 기능이 각각 있을 때, 정상적으로 동작할 수 있다. 하지만 A + B 와 같이 사용된다면 실제 결과는 어떻게 나올지 모른다. 이러한 여러 모듈이 복잡하게 상호작용할 수록 이를 예측하는 건 더 어렵다. 이처럼 Business Layer 와 Persistence Layer를 통합한 테스트를 진행하면 더 높은 신뢰성을 보장할 수 있다.또한 Persistence Layer에 대한 테스트를 이미 작성했다면 굳이 Persistence Layer를 Mocking하지 않는 것이 더 비용적으로 합리적일 수 있다. Presentation Layer는 외부 세계와 연결된 계층이다. 외부 세계로 받은 정보에 대한 검증이 필요하다.도메인의 성격, 서비스에 대한 내용과는 무관하다. 따라서 굳이 도메인 관련 로직을 검증할 필요는 없다고 생각한다.물론 각 모듈이 상호작용할 때와 각각 작용할 때는 다른 결과값이 나올 수 있기에 3가지 계층에 대한 검증을 한 번에 하는 것이 더 신뢰성을 보장할 것이다.그렇지만 비용 또한 고려해야 하므로 수동 테스트로 마무리하거나 비용이 나오더라도 꼭 검증해야할 정도로 중요하다면 그때 모든 계층에 대한 통합 테스트를 작성하여 신뢰성을 보장하면 된다고 생각한다.    

백엔드테스트Layered-Architecture

Practical Testing 3주차 발자국

서문테스트에 대해서는 늘 많은 고민이 있었다.테스트를 작성하다보니 많은 선택지들이 있었는데 이번 3주차 간의 강의를 통해서 이전에 수강했던 강의를 복습하는 차원에서 다시 한 번 바라보았다.특히나 처음에 강의를 들었을 경우에 가장 와닿고 도움이 많이 되었던 말은 테스트 코드는 좋은 구조를 설계해나가는 과정이라는 것이다.테스트를 작성해나가다 보면 내가 짜놓은 코드의 설계가 확장성 있게 작성이 되지는 않았는지, BDD 기반으로 테스트 코드를 작성해 나가면서 내가 짠 메서드의 행위가 테스트 하기 쉽도록 작성되어 있는지에 대해서 쉽게 파악 할 수 있었다.취업 전에 이 강의를 보고 난 이후의 경험과 현재 취업을 하고 나서 여러가지 테스트 서적을 보고 난 이후의 경험이 합쳐져서 더 많은 도움이 되고는 한다.단위 테스트기존에는 단위 테스트의 기준을 항상 비즈니스 레이어 기준으로 잡았었다. 단위 테스트는 책 한권으로 다룰 만큼 되게 폭 넓은 분야인데 그렇게 넓게 다룰 필요가 있는가? 라고 생각을 하기도 하였다.하지만, Effective Software Testing 책에서 읽어 본 바로는 하나의 계층만 테스트 할 수 있는 구조라면 단위 테스트이다.라는 말이 나의 모호함을 해결해주었다. 다른 말로, 통합 테스트와 단위 테스트의 기준은 무엇을 바라보아야 하지? 라고 했을 때, 다른 계층과의 결합이 생기는 순간 이는 통합 테스트를 바라보면 된다는 것이다.이 관점에서 통합 테스트를 작성 할 때 Mocking 하는 것을 더 생각해 볼 필요가 있다. Mock은 주어진 행위에 대한 결과가 내가 예측한 값을 사용하게 된다. 이는, 실제로 그 값이 내려오지 않을 수 있음을 시사하는데 이로써 해당 계층에 대한 테스트가 신뢰도 있는 테스트가 작성되지 않는다는 것이다.따라서, Mock을 사용한 테스트를 하게 되면 이는 단위 테스트를 작성하는 형태가 된다. 내 계층에서 어떠한 메서드가 이 값을 받았을 때 해당 계층의 결과는 이 결과가 나와야 해. 라는 의미를 가지게 되는 것이다.프레젠테이션 레이어이 부분은 강의를 보면서도 아직 고민이 많다.우리는 프레젠테이션 레이어 테스트를 작성하면서 강의에서도 컨트롤러 영역에서의 서비스 계층 영역을 mocking 하여 결과 값을 가지고 오는 것을 볼 수 있다. 물론 전제는 서비스가 테스트되었기 때문에 서비스 계층에서의 결과 값은 예측 값이 맞겠지만, 이는 서비스 영역에서 결과 값이 필드가 바뀌는 경우에 놓칠 수 있는 경우를 시사하게 된다.물론 이 부분은 개발자가 신경써야 하는 부분이기도 하지만 휴먼 에러를 방치하는 것은 또 좋지 않다고 생각을 하긴 한다. 이 방법을 해결하기 위해서는 결국 E2E (End-To-End) 테스트를 진행하기 위해서 assured 테스트를 사용해보기도 하였는데, 이럴 경우에 Persistent 레이어까지 전부 test container를 띄우면서 실 상황과 유사한 상황으로 테스트를 진행하다보니 테스트에 많은 시간이 소요되게 된다.물론, 우빈님이 소개해주신 스프링 부트 컨테이너의 빈 목록을 한데 모아서 관리한다면 속도 개선은 많은 부분 이루어지지만, 그 이전에 테스트를 실행 할 때마다 해당 테스트에 필요한 데이터들을 새로이 만들어주는 과정이 많은 시간을 잡게 되고, 이는 결국 비즈니스 개발에 많은 시간을 잡아먹게 된다.추가적으로, E2E로 작성하게 되는 경우에 나타나는 문제점은 어찌됐든 서비스 영역의 테스트 결과 값이 서비스 계층의 비즈니스가 완성되기전까지 문서화를 위하여 Dummy 형태의 응답 값을 가지게 된다는 것이다.이러한 여러가지 상황 속에서 아직까지 나만의 답을 찾지 못하였으니 계속 경험을 가지면서 적절한 나만의 기준을 찾는 것이 좋을 것 같다. 해당 내용에 대해서는 우빈님께 질문을 드릴 예정이다.영속성 레이어이 부분도 많은 고민을 가지고 있었다. 취업 준비를 할 때에는 H2 인메모리 DB를 사용하여 많은 부분 영속성 레이어의 테스트를 구성 할 수 있었으나, 실제로 현업에 왔을 때에는 H2에서는 정상 동작하나 실제 DB에서는 작성하지 않는 상황들이 적지 않게 발생하는 것을 볼 수 있었다. 이를 대체 할 방법이 Test Container를 띄워서 테스트를 작성하는 것인데, 시간이 생각보다 오래 걸린다. 아직 이 부분도 해결이 되지 않은 것 같다. 우빈님 강의 뿐만이 아니라 더 많은 서적들을 보면서 영속성 계층을 어떻게 처리하는게 좋을지 이 부분도 경험치를 쌓아나가야 하는 것 같다.모든 상황에서는 은탄환은 존재하지 않는다. 실제로 어떻게 하는지 지금 내 레벨에서는 최대한 카피하여 많은 경험치를 쌓아가는 과정이 중요하다고 생각한다. 또 좋은 기술이 나오지 않을까? 라는 생각이 들기도 한다.마무리옛날에는 테스트를 짜는데 시간이 오래걸린다는 말이 어느정도 맞는 말이라고 생각한다. 하지만, 현재에는 기술이 너무 좋아졌다. copilot을 사용하면서 테스트를 짜는 시간을 거의 300%는 더 빠르게 작성 할 수 있게 해주는 것 같다.기술을 적절히 활용하면서 회사 내에 테스트를 짜는 문화를 좀 더 거부감 없게 만들어나가고 싶다.

소프트웨어 테스트테스트단위테스트회고

zooxop

인프런 워밍업 스터디 클럽 2기 백엔드(클린코드&테스트코드) 발자국 - 3주차

인프런 워밍업 클럽 2기, 백엔드(클린코드&테스트코드) 과정에 참여하고 있습니다.이번 3주차에는 테스트에 대한 이론적인 내용과 TDD, 실용적인 테스트 방법등을 학습했습니다.강의 링크: Readable Code: 읽기 좋은 코드를 작성하는 사고법 [학습 요약] 테스트의 필요성왜 작성해야 할까?사람이 수동으로 직접 테스트하는 데에는 한계가 있다.테스트 코드 작성을 통해 얻는 이점빠른 피드백자동화안정감테스트 코드를 작성하지 않는다면..변화가 생기는 매순간마다 발생할 수 있는 모든 Case를 고려해야 한다.변화가 생기는 매순간마다 모든 팀원이 동일한 고민을 해야한다.빠르게 변화하는 소프트웨어의 안정성을 보장할 수 없다.테스트 코드가 병목이 된다면..프로덕션 코드의 안정성을 제공하기 힘들어진다.테스트 코드 자체가 유지보수하기 어려운, 새로운 짐이 된다.잘못된 검증이 이루어질 가능성이 생긴다.올바른 테스트 코드는,자동화 테스트로 비교적 빠른 시간 안에 버그를 발견할 수 있고, 수동 테스트에 드는 비용을 크게 절약할 수 있다.소프트웨어의 빠른 변화를 지원한다.팀원들의 집단 지성을 팀 차원의 이익으로 승격시킨다.가까이 보면 느리지만, 멀리 보면 가장 빠르다.테스트는 귀찮다. 귀찮지만, 해야한다! 단위 테스트작은 코드 단위를 독립적으로 검증하는 테스트작은 코드: 클래스 or 메서드검증 속도가 빠르고, 안정적이다.외부에 의존을 하지 않기 때문에JUnit5단위 테스트를 위한 테스트 프레임워크XUnit - Kent BeckSUnit(SmallTalk), JUnit(Java), NUnit(.NET), DUnit(Delphi)AssertJ테스트 코드 작성을 원할하게 돕는 테스트 라이브러리풍부한 API, 메서드 체이닝 지원테스트 케이스 세분화하기항상 질문하기: 암묵적이거나, 아직 드러나지 않은 요구사항이 있는가?해피 케이스와 예외 테스트를 도출해낼 수 있어야 한다.경계값 테스트경계값?: 범위(이상, 이하, 초과, 미만), 구간, 날짜 등테스트하기 어려운 영역을 구분하고 분리하기테스트하기 어려운 영역을 갖고 있는 코드가 추가된다면, <u>전체 테스트가 망가질 가능성</u>이 생긴다.예시)LocalDateTime.now() 를 사용하는 기능을 테스트할 때, 테스트를 실행하는 시간에 따라 성공할수도, 실패할수도 있게 됨.이 때, LocalDateTime.now() 를 사용하는 코드가 곧 테스트하기 어려운 영역이 된다.이 때, 테스트가 어려운 영역을 외부에서 주입받도록 변경하면 테스트 하기 수월해진다.외부로 분리할수록, 테스트 가능한 코드는 많아진다.테스트하기 어려운 영역 코드 예시관측할 때마다 다른 값에 의존하는 코드현재 날짜/시간, 랜덤 값, 전역 변수/함수, 사용자 입력 등외부 세계에 영향을 주는 코드표준 출력, 메시지 발송, 데이터베이스에 기록하기 등테스트하기 쉬운 영역 코드 예시순수 함수같은 입력에는 항상 같은 결과 반환외부 세상과 단절된 형태 (전역 변수를 변경하지 않는) TDD프로덕션 코드보다 테스트 코드를 먼저 작성하여 테스트가 구현 과정을 주도하도록 하는 방법론[Red - Green - Refactor] 3단계 구조로 순환하며 진행Red: 실패하는 테스트 작성의도적으로 실패하게 테스트를 작성하라는게 아니다.도메인적 지식을 기반으로, 테스트가 통과해야만 하는 조건으로 테스트 코드를 작성하라는 의미.ex) 테스트에서는 10 이라는 값이 나올것을 기대하지만, 구현이 미완성이라서 0이 리턴되는 경우에 기대값을 변경하지 않고 그대로 두는 것을 말한다.Green: 테스트 통과, 최소한의 코딩최대한 빠르게 테스트 통과만을 위한 코딩을 수행.TDD 방법론에서는 하드코딩도 허용.Refactor: 구현 코드 개선, 테스트 통과 유지구현부 코드를 변경한 다음에도 테스트가 통과 된다 -> 구현이 올바르게 되었다 라고 판단할 근거가 된다.피드백TDD가 제공하는 핵심 가치테스트 코드를 통해 내가 구현한 프로덕션 코드에 대해서 자주, 빠르게 피드백을 받을 수 있다.기능 구현 후 테스트 작성 vs. 테스트 작성 후 기능 구현선 기능 구현, 후 테스트 작성 케이스테스트 자체의 누락 가능성특정 테스트 케이스(해피 케이스)만을 검증할 가능성잘못된 구현을 다소 늦게 발견할 가능성선 테스트 작성, 후 기능 구현복잡도가 낮은, 테스트 가능한 코드로 구현할 수 있게 한다.복잡도가 낮다?: 유연하고, 유지보수하기 쉽다쉽게 발견하기 어려운 엣지(Edge) 케이스를 놓치지 않게 해준다.구현에 대한 빠른 피드백을 받을 수 있다.과감한 리팩토링이 가능해진다.TDD: 관점의 변화TDD 이전의 관점: 테스트는 구현부 검증을 위한 보조 수단TDD 이후의 관점: 테스트와 상호 작용하며 발전하는 구현부TDD는 클라이언트 관점에서 피드백을 주는 Test Driven 도구이다. 테스트는 "문서"다테스트는 "문서"다. 왜?프로덕션 기능을 설명하는 테스트 코드 문서다양한 테스트 케이스를 통해 프로덕션 코드를 이해하는 시각과 관점을 보완어느 한 사람이 과거에 경험했던 고민의 결과물을 팀 차원으로 승격시켜서, 모두의 자산으로 공유할 수 있다.우리는 항상 팀으로 일한다 DisplayName을 섬세하게명사의 나열보다 문장으로 작성하자테스트 행위에 대한 결과까지 기술하기before: 음료 1개 추가 테스트after: 음료를 1개 추가할 수 있다.도메인 용어를 한층 추상화된 내용을 담기메서드 자체의 관점보다, 도메인 정책관점으로 생각하자.테스트의 현상을 중점으로 기술하지 말 것before: 특정 시간 이전에 주문을 생성하면 실패한다.after: 영업 시작 시간 이전에는 주문을 생성할 수 없다.BDD, Behavior Driven DevelopmentTDD에서 파생된 개발 방법함수 단위의 테스트에 집중하기 보다, 시나리오에 기반한 테스트케이스(TC) 자체에 집중하여 테스트한다.개발자가 아닌 사람이 봐도 이해할 수 있을 정도의 추상화 수준(레벨)을 권장Given / When / ThenGiven: 시나리오 진행에 필요한 모든 준비 과정 (객체, 값, 상태 등)When: 시나리오 행동 진행Then: 시나리오 진행에 대한 결과 명시, 검증 Spring & JPA 기반 테스트 레이어드 아키텍처와 테스트Layered Architecture 는 관심사의 분리를 위해 수행한다!책임을 나누고, 유지보수하기 용이하게 만들자Spring & JPA 기반 애플리케이션에서 일반적으로 사용하는 레이어 구조User <-> [Presentation Layer] <-> [Business Layer] <-> [Persistence Layer] <-> DB통합 테스트여러 모듈이 협력하는 기능을 통합적으로 검증하는 테스트일반적으로 작은 범위의 단위 테스트만으로는 기능 전체의 신뢰성을 보장할 수 없다.풍부한 단위 테스트 & 큰 기능 단위를 검증하는 테스트Spring / JPA 훑어보기스프링은 Library? Framework?라이브러리는 내 코드가 주체가 된다.프레임워크는 말 그대로 이미 프레임이 짜여있고, 그 프레임에 짜맞추는 방식으로 코드를 작성하게 됨.Spring IoC(Inversion of Control)DI(Dependency Injection)AOP(Aspect Oriented Programming)ORM (Object-Relational Mapping)객체 지향 패러다임과 관계형 DB 패러다임의 불일치이전에는 개발자가 객체의 데이터를 한땀한땀 매핑하여 DB에 저장 및 조회 (CRUD)ORM을 사용함으로써 개발자는 단순 작업을 줄이고, 비즈니스 로직에 집중할 수 있다.JPA (Java Persistence API)Java 진영의 ORM 기술 표준인터페이스이고, 여러 구현체가 있지만 보통 Hibernate를 주로 사용한다.반복적인 CRUD SQL을 생성 및 실행해주고, 여러 부가 기능들을 제공한다.편리하지만 쿼리를 직접 작성하지 않기 때문에, 어떤 식으로 쿼리가 만들어지고 실행되는지 명확하게 이해하고 있어야 한다.Spring 진영에서는 JPA를 한번 더 추상화한 Spring Data JPA 제공QueryDSL과 조합하여 많이 사용한다.타입 체크, 동적 쿼리JPA 에서 주로 사용되는 어노테이션들@Entity, @Id, @Column@ManyToOne, @OneToMany, @OneToOne, @ManyToMany@ManyToMany: 일대다 - 다대일 관계로 풀어서 사용한다. [후기]본격적으로 테스트 코드에 대한 학습을 시작한 주간이었다. 개발자로 일을 한지 꽤 오래되었지만 테스트를 작성해본 적이 많지 않아서 테스트를 작성하는것에 언제나 애를 먹었었는데, 기초적이고 이론적인 내용부터 자세히 공부하고 예제로 따라해보니 지금까지 잘 모르고 지금까지 완벽하게 이해하지 못하고 넘어갔었던 내용들을 어느정도 해소할 수 있었던 것 같아 좋았다.

백엔드워밍업클럽백엔드테스트클린코드

채널톡 아이콘