블로그

dohi

워밍업 클럽 4기 - 백엔드 3주차 회고

수정내역썸네일 추가3주차 회고 강의 수강레이어드 아키텍처와 계층별 테스트 전략이번 주에는 Spring 기반의 레이어드 아키텍처 구조를 중심으로,Persistence / Business / Presentation Layer 각각의 책임과 역할을 이해하고이에 적절한 단위 및 통합 테스트를 직접 구성해보는 학습을 진행했습니다.1. Persistence Layer 테스트Persistence Layer는 애플리케이션의 가장 하단에서 데이터 접근(CRUD)만을 책임지는 레이어(data access의 역할)비즈니스 로직은 이 레이어에 포함되지 않아야 하며, 주로 JPA 기반 Repository를 테스트 대상으로 삼습니다.사용 기술: Spring Data JPA, @DataJpaTest테스트 대상: Repository + JPA 동작 확인특징실제 DB와 유사하게 동작하는 경량 통합 테스트 수행DB 변경 없이 테스트 가능 (자동 롤백) 2. Business Layer 테스트Business Layer는 도메인의 핵심 로직을 처리하는 계층으로, 서비스 클래스가 주를 이룹니다.Persistence Layer와 상호작용하며 트랜잭션을 보장하는 것이 중요합니다.테스트 대상: Service 클래스, 로직 흐름사용한 테스트 기법given-when-then 패턴 검증 내용:입력에 따라 기대하는 응답이 나오는지트랜잭션 롤백이 제대로 작동(@Transactional)@SpringBootTest, @DataJapTest차이 DataJpaTest 는 트랜잭션 어노테이션이 있어 자동 롤백 3. Presentation Layer 테스트Presentation Layer는 외부 요청(HTTP 등)을 가장 먼저 받는 계층으로,파라미터 검증이나 라우팅 처리 등의 역할을 담당합니다.테스트 방식: 컨트롤러 단위 테스트 + 나머지 계층은 Mocking학습한 포인트:@Transactional(readOnly = true)를 클래스 단에 선언하여 조회 메서드는 최적화CUD 작업은 개별 메서드에 @Transactional을 덮어써 트랜잭션 활성화동시성 이슈를 고려한 설계 및 분리 4. 테스트 환경 구성application.yml을 통해 프로파일(local/test) 분리테스트 환경에 따라 다른 DB, 포트, 설정이 적용되도록 구성테스트 도중 DB 변경 방지를 위해 자동 롤백 + @AfterEach 정리 수행어노테이션역할@SpringBootTest전체 애플리케이션 컨텍스트 로딩@DataJpaTestJPA 관련 빈만 주입, 빠른 테스트 가능미션Day 11https://github.com/Goddohi/warming-up-readable-code/tree/study/day11 회고이번 주는 단순히 테스트 코드를 작성하는 것이 아니라각 계층의 역할에 맞춘 책임 분리와 테스트 전략 수립의 중요성을 몸소 느낄 수 있던 시간이었다.특히 레이어드 구조를 이해하고,Persistence는 데이터 검증에만 집중하고Business는 로직 흐름과 트랜잭션을,Presentation은 외부 입력에 대한 응답 및 검증에만 집중하는테스트의 방향성과 목적을 처음 명확히 잡아볼 수 있었다.이번에는 회사 출근과 프로젝트가 야간근무도 몰려서 많은 정리를 못해서 중구난방이라아직 잘 이해를 못한 점이 많다.더 한번더 복습이 필요하다.

백엔드백엔드워밍업클럽4기워밍업클럽

dohi

워밍업 클럽 4기 - 백엔드 2주차 회고

수정내역썸네일 추가2주차 회고강의 수강리팩토링에 대해서 배운점을 회사 본업을 하면서 실전 적용도 해봤다회사에서는 리팩토링에 대해서 왜 그렇게 수정하는지 잘 적어만 준다면 다 허락을 해줬다.실제로 중복 코드가 많은 부분은 리팩토링으로 정리 시켰고강의를 보면서 중요시 생각한 점객체에게 바로 예의없이 꺼내지않기객체에 대한 메서드로 해결 주석에 대한 양면성회사에서 관련된 주석이 많았는데몇몇개는 친절한 주석도 있었으나 너무 주석이 많았다.좋은 주석을 달아보려고 해야한다.변수와 메서드의 나열 순서테스트만들고 테스트하는 것은 통과하기 위한 테스트를 제작할 가능성이 있고테스트를 하고 만들면 통과 하기 위한 기능을 만들 수가 있다라는 생각.미션Day 7https://github.com/Goddohi/warming-up-readable-code/tree/study/studycafe회고회고에 적으려고 미션을 아주 짧게 적었었는데코드 리뷰를 멘토님께서 디테일 하게 해주셨는데코드 리뷰를 해주시면서 선배개발자로써의 개념으로 이야기를 해주셧고 다른 사람의 코드를 볼 수있는 경험이 좋았다.이번주는 회사일이 많이 바빠서 이동하면서 듣느라 작성할 만한 문건을 만들지 못했지만들을때마다 도움되는 개념이였던 만큼 한번더 다시 한번더 듣고 정리 해야겠다

워밍업클럽백엔드워밍업클럽4기

dohi

워밍업 클럽 4기 - 백엔드 1주차 회고

수정내역썸네일 추가1주차 회고강의 수강‘추상과 구체’ 과제를 통해 추상 개념에 대해 다시 한번 생각해볼 수 있었다.매직 넘버를 접하며, 추상적인 값은 별도 변수로 분리해 명확하게 표현하는 것이 중요하다는 점을 알게 되었다.추상화 레벨 개념을 통해 코드의 표현 단계와 책임 분리에 대해 고민해보는 계기가 되었다.내 코드를 분석하면서, 읽는 사람이 뇌의 메모리를 얼마나 사용할지 생각해보았고, 얼리 리턴을 적용해 리팩토링을 시도했다.특히 ‘부정어를 대하는 자세’에서는 코드에 부정 표현이 들어갈 때의 가독성과 인지 부담에 대해 다시 생각해보게 되었다.이점은 왜 이렇게 사용해야할 까 라는 생각을 했었지만 SOLID에서 이렇게 많이 사용하는 구나를 하면서 배우게 되었다.과제를 직접 수행하면서, 배운 내용을 실전에서 적용해보며 리팩토링 감각을 조금씩 익혀갔다.SOLID 원칙 각 요소를 복습할 수 있었고, 이를 실제 코드에 어떻게 적용해볼 수 있을지 고민해보는 기회가 되었다.특히 getter setter에 사용에 대해서 고려를 많이 하게 되었다.  미션Day 2추상을 고려할때 일단 사람들 간에 약속적인 의미로 생각하였다구체에는 자세하게 적었지만 불필요한 내용을 간추리기 라고 생각하였다. Day 4AS-IS에 있는 코드를 다 분석하고 TO-BE에는 배운 코드를 다 이용하려고 하였다해당 과제에 자세하게 적긴했지만일단 미리 리턴을 하도록 정리하였고부정어 처리그후 예외처리로 마무리하였다. SRP :한 클래스는 딱 하나의 역할만 하자.OCP :기존 코드를 고치지 않고, 기능을 확장할 수 있어야 한다.LSP:부모 타입을 사용하는 곳에 자식 타입을 써도 제대로 작동해야 한다.ISP :쓸데없는 인터페이스는 나눠서 큰 종합세트를 주지말고 작은 인터페이스 여러개가 좋다DIP:고수준 모듈은 저수준 모듈에 의존하지 말고, 추상(인터페이스)에 의존하자.

백엔드워밍업클럽워밍업클럽4기백엔드워밍업클럽백엔드

이강호

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

👣1주 차 발자국 ✅ 강의 요약- 추상 우리가 클린 코드를 추구하는 이유 - 가독성이해가 잘 된다.유지보수 하기 쉬워진다. 시간과 자원이 절약된다.클린코드를 관통하는 주제 - '추상' 추상이란?중요한 정보는 가려내어 남기고, 덜 중요한 정보는 생략하여 버리는 것.적절한 추상화는 복잡한 데이터와 복잡한 로직을 단순화하여 이해하기 쉽도록 돕는다. 적절한 추상화는 무엇인가?이름 짓기단수,복수 구분이름 줄이지 않기은어/방언 사용 금지비슷한 상황에서 자주 사용하는 단어, 개념 습득하기  메서드로 추상화한 메서드의 주제는 하나추상화된 구체를 유추할 수 있는 적절한 메서드 선언부 구성추상화 레벨 동등하게 구성하나의 세계 안에서는 추상화 레벨이 동등해야 한다. 매직 넘버, 매직 스트링의미 있는 숫자,문자열은 상수로 추출해 가독성과 유지보수성을 높인다.- 논리, 사고의 흐름아래의 과정들을 통해 읽기 좋은 코드를 만들어 뇌 메모리를 적게 쓰게 한다. early returnelse 사용을 지양해 아래쪽 코드를 읽을 필요를 줄인다.사고의 depth 줄이기추상화를 통해 사고의 depth를 줄이고, 사용할 변수는 가깝게 설정한다.공백 라인의미 단위를 공백라인으로 나누어 가독성을 높인다.부정어 부정어구를 사용 하지 않아도 된다면, 사용 안하게끔 리팩토링 한다.부정의 의미를 담는 다른 언어나 부정어구로 메서드명을 만들어 부정 연산자 사용을 지양한다.예외처리예외 발생할 가능성을 낮춘다.의도한 예외와 예상하지 못한 예외를 구분해 작성한다.항상 NPE를 생각하자.- 객체 지향 패러다임객체란 결국 추상화된 데이터 + 코드객체가 여러개 생기면서 객체간 협력과 책임을 구분하는게 중요해짐. 객체간 협력, 책임의 관점에서 추상화를 생각해야 한다.관심사에 따라 객체를 나눈다.높은 응집도, 낮은 결합도가 생긴다.객체에 메세지를 던져야 한다.getter/setter를 지양한다. - SOLIDSRP: 하나의 클래스는 하나의 변경 이유만을 가져야 한다.OCP: 기존의 코드 수정없이 시스템의 기능을 확장할 수 있어야 한다.LSP: 상속 구조에서, 자식클래스는 부모 클래스의 책임을 준수하며, 부모 클래스의 행동을 변경하지 않아야 한다.ISP: 클라이언트는 자신이 사용하지 않는 인터페이스에 의존하면 안 된다. (그럴 경우, 인터페이스를 분리해야 한다.)DIP: 상위 수준의 모듈은 하위 수준의 모듈에 의존해서는 안 된다. - 객체 지향 적용하기상속과 조합상속은 결합도가 높다.중복이 생기더라도 조합과 인터페이스를 활용하자.Value Object불변성, 동등성, 유효성을 보장해야하며 도메인의 개념을 추상화한 객체Entity식별자가 존재. 식별자가 같으면 동등한 객체로 취급한다.일급 컬렉션컬렉션을 Wrapping한 객체컬렉션을 추상화시켜 가공된 로직을 넣을 수 있다.getter로 반환 해야 할 경우, 새로운 컬렉션을 만들어서 반환Enum상수의 집합상태와 행위를 한 곳에서 관리할 수 있는 추상화된 객체변경이 잦으면 DB로 관리하는 걸 고려다형성 활용 변하는 것과 변하지 않은 것을 구분해서 추상화숨겨져 있는 도메인 개념 도출하기도메인 지식은 만드는 것이 아니라 발견하는 것변경이 많이 일어날 것 같은 미래를 예측해서 최선의 설계를 하고, 다시 돌아올 수 있도록 코드를 만들자.✅ 강의 회고코드를 따라 치면서 많이 학습했다고 느꼈지만, 어느 학습이던 복습이 중요하겠지만 개념적인 부분은 특히 복습이 중요한 것 같다. 강의 예제(코드)가 좋아서 다시 돌려서 리팩토링 해보면서 따라가니까 좀더 확실하게 얻을 수 있었다.학습 시간적인 측면에서 관리를 잘 못해서 처음에 몰아 듣고, 마지막에 몰아 듣게 되었는데 강의가 주제별로 잘 나뉘어져 있기 때문에 시간 관리를 위해서라도 잘 쪼개서 시간관리를 잘 해서 완강과 학습에 도움이 되도록 해야할 것 같다. ✅ 미션 과정특별히 어렵게 고민한 것은 없었다.추상과 구체, SOLID는 일상생활에서 자주 볼 수 있는 것으로 나타내면 좋을 것 같아 각각 자동차, 스마트폰에 빗대어 표현했다. ✅ 미션 회고'자기만의 언어로 키워드 정리하기' 가 정말 중요한 것 같다.어떤 분야던 학습하면서 머리에 이미지화 해서 기억한다던가 하면 오래 기억한다고 하는데, 이번 과정에서 그것을 느꼈다.SOLID 원칙을 달달 외우기는 했지만 설명을 한다면 어떻게 ...?이번 미션을 통해 자신만의 언어로 이미지화 했기 때문에 누구에게 설명할 때는 딱딱한 텍스트 보다는 부드럽게 설명할 수 있을 것 같다.강의 - Readable Code: 읽기 좋은 코드를 작성하는 사고법

백엔드워밍업클럽4기워밍업클럽백엔드

워밍업클럽4기 DevOps 1주차 발자국

복습글(링크)Sprint1 Day2 컨테이너 한방 정리컨테이너의 기술 발전 과정 흐름을 통해, 컨테이너 오케스트레이션에 꼭 필요한 지식을 알 수 있어서 좋았습니다.현업에서 도커를 사용하고 있었지만, LXC 및 rkt에 대해서는 알지 못했는데, 이번 기회에 관련 지식을 습득할 수 있어서 매우 좋았습니다.쿠버네티스 설치에서 사용된 containerd 등 도커에 관련된 자세한 이야기들을 들을 수 있어서 좋았습니다. Sprint1 Day3 쿠버네티스 무게감 있게 설치하기쿠버네티스 권장 설치 조건에 대해서 설명 들을 수 있었고, 처음 사용해본 Vagrant 가 매우 신기했습니다.강사님께서 스크립트 내용도 추가로 설명을 해주셔서, 해당 스크립트를 수정해 원하는 환경도 구성해보고 싶습니다. Sprint1 Day4 실무에서 느껴본 쿠버네티스가 편한 이유실무에서 VM 기반으로 App을 배포할 때 공수와 쿠버네티스를 사용해 App을 배포할 때 효율성을 이해할 수 있었습니다.App을 초기 런칭한다고 가정하며 Pod에 부하를 줄 때, 쿠버네티스가 자동으로 Pod를 스케일링되는 지 실습할 수 있었으며셀프힐링을 통해 Pod가 재시작되어 운영자의 실수에 유연하게 대처할 수 있는 부분이 특히 좋았습니다. docker logs 를 사용해 로그를 일일이 찾고 있었는데, Loki를 사용해 App의 로그를 웹 콘솔에서 빠르게 확인할 수 있는 부분도 너무 좋아 빠르게 업무에 도입해보고 싶습니다. Sprint1 Day5 Object 그려보며 이해하기쿠버네티스 배포에 사용된 YAML 파일에서, 오브젝트와 각 값이 어떤 역할을 하는지 자세히 알아볼 수 있었습니다.쿠버네티스에서 권고하는 Label 지정과 강사님께서 권고하는 Label 지정이 필요한 값에 대해 알려주셔서 도움이 많이 되었습니다. 

데브옵스 · 인프라워밍업클럽4기

이강호

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

👣4주 차 발자국 ✅ 강의 요약섹션 7. Mock을 마주하는 자세 Test double 테스팅이 어려운 경우, 실제 객체를 대신해 테스트를 진행할 수 있도록 만들어주는 객체DummyFakeStubSpyMock Dummy아무 것도 하지 않는 깡통 객체객체는 전달되지만 사용되지 않는 객체이다.Fake단순한 형태로 동일한 기능은 수행하지만, 프로덕션에 쓰기에는 부족한 객체Stub테스트에서 요청한 것에 대해 미리 준비한 결과를 제공하는 객체고정값을 반환하는 객체로, 정해진 응답이 필요시 사용SpyStub이면서 호출된 내용을 기록하여 보여줄 수 있는 객체일부는 실제 객체처럼 동작시키고, 일부만 스터빙 할 수 있다.Mock행위에 대한 기대를 명세하고 그에 따라 동작하도록 만들어진 객체Stubbing ?Mock 객체의 행동을 조작하는 것.Stub은 메서드가 특정 값을 반환하도록 설정하기 때문에, 반환값에 대한 검증을 한다.Mock은 특정 메서드가 올바르게 호출 되었는지 검증한다.Stub 은 상태 검증, Mock 은 행위 검증Mokito 주요 애너테이션@Mock, @Spy, @InjectMocks, @MockBean, @SpyBean각 애너테이션 요약 - 링크BDDMockitoBDD 스타일로 Mockito를 사용할 수 있도록 지원해주는 라이브러리Mockito를 상속해서 구현한 객체로써 모든 기능은 Mockito와 동일 섹션 8. 더 나은 테스트를 작성하기 위한 구체적 조언하나의 테스트에 하나의 검증만 수행분기문, 반복문 등의 논리 구조를 지양테스트 케이스 별로 각각의 테스트 코드를 작성제어 가능한 테스트가 되도록 작성LocalDate.now(), LocalDateTime.now() 와 같이 외부요인으로 제어가 불가능한 경우는 사용하지 않는 게 좋다.제어할 수 없는 값을 제어 가능하게 변경하자.테스트 간 독립성 보장외부 조건, 순서, 공유 상태 등에 영향을 받지 않고 항상 동일한 조건에서 수행되어야 한다.Test Fixturegiven절에 테스트의 픽스처를 구성한다. (가독성과 유지보수 측면에서 유리) 별도의 data.sql 데이터를 추출하지 않는다.단위테스트 내에서 모두 표현한다. data.sql과 같은 공통 로직으로 테스트 픽스처 구성하지 않기픽스처 생성 로직은 같은 테스트 클래스에서 관리하기테스트코드 안에 메서드를 추출할 때, 정말 필요한 값만 파라미터로 받기 deleteAll(), deleteAllInBatch()엔티티 하나씩 remove() VS 한 번에 삭제 (bulk delete)deleteAll()은 다수의 쿼리 발생으로 테스트 비용이 증가할 수 있다. deleteAll()은 연관된 엔티티가 삭제되고, deleteAllInBatch()는 삭제가 안된다.용도에 맞게 각 메서드를 적절히 사용해야 한다.  @ParameterizedTest, @DynamicTest@ParameterizedTest동일한 테스트를 다른 입력값으로 테스트 할 때 사용@ValueSource, @CsvSource, @MethodSource 등 다양한 소스로부터 테스트가 가능하다.@DynamicTest테스트를 실행할 때 동적으로 생성하는 방식@TestFactory 를 사용한다.섹션 9. Appendix지만 중요한 것들학습 테스트잘 모르는 기능, 라이브러리, 프레임워크를 학습하기 위한 테스트 코드여러 테스트 케이스를 스스로 정의하고 검증하는 과정을 통해 구체적인 동작과 기능을 학습Spring Rest Docs테스트 코드를 통한 API 문서 자동화 도구API 명세를 문서로 만들고 제공함으로써 협업을 원활하게 한다.✅강의 회고 Layer 별로 어느 도구들을 사용하며 테스트를 해야 하는지 알 수 있었다. 특히 Presentation layer에서 Mock 사용법이 가장 기억에 남을 것 같다. 아무래도 테스트 코드를 작성 안하고 수동으로 테스트를 해왔을 때, Presentation layer의 결과를 직접 보면서 했기 때문에 이 부분을 어떻게 자동화를 할 수 있을지에 대한 궁금증이 해결되는 순간이였다. Mock 애너테이션별 사용법을 배운 것도 앞으로 테스트 코드를 작성하는데 큰 도움이 될 것 같다고 느꼈고, 강의 내내 TDD로 추가 요구사항을 해결해 나가는 점이 TDD 방식을 자연스럽게 익히게 된 것 같아 학습에 뭔가 여운이 남는다.그 외에도 BaseEntity 사용과 같은 클린 코드를 위한 설계와 리팩터링도 강의 내내 이루어져 복습까지 함께 하는 느낌이였다.아마 강의를 다시 들어야 온전히 습득할 수 있겠지만, 이번 첫번째에도 많은걸 얻을 수 있었던 것 같다.✅ 미션 과정 & 회고강의내용을 떠올리면서 계층별로 어느 부분에 중심적으로 테스트 해야하는지를 떠올리며 BDD 패턴을 적용해 해결 했다.예제게 우리가 친숙한 댓글 로직 이여서 효율적인 테스트에만 집중해서 해결 할 수 있었던 것 같다.초기화 부분에 대한 부분도 고려하면서 미션을 했다.

백엔드워밍업클럽4기워밍업클럽백엔드

인프런 워밍업 클럽 4기 DevOps - 4주차 발자국

이번 주 카테고리Helm과 Kustomize 비교하며 사용-2ArgoCD 빠르게 레벨업-1ArgoCD 빠르게 레벨업-2ArgoCD 빠르게 레벨업-3이번 주 회고이번 주는 다른 주차에 비해 학습 분량은 적었지만, 내용의 난이도는 훨씬 높았다. Helm과 Kustomize는 쿠버네티스 리소스를 템플릿화하거나 커스터마이징할 수 있는 도구로 알고 있었지만, 실제로 실습을 해보니 각 도구의 철학과 사용 방식이 달라 적응하는 데 시간이 걸렸다.ArgoCD는 GitOps라는 개념을 바탕으로 한 배포 도구로, 겉보기에는 UI도 잘 갖춰져 있어 쉬워 보였지만, 내부 구조와 동작 원리를 이해하려다 보니 예상보다 복잡했다. 특히, 실습을 따라 진행하면서 발생한 예상치 못한 동작이나 버그의 원인을 파악하는 과정은 단순히 기능을 익히는 수준을 넘어선 깊은 이해를 요구했다.이번 주는 "양보다 질"의 한 주였다. 적은 분량 속에 담긴 고밀도의 개념들을 소화하려다 보니 시간이 훨씬 더 많이 소요되었고, 단순히 따라하는 수준을 넘어서 각 도구의 역할, 차이점, 그리고 실전에서 어떻게 활용해야 할지를 끊임없이 고민하게 되었다.앞으로도 관련 문서와 다른 개발자들의 블로그, GitHub 예제들을 참고해 내용을 다듬고, 실습을 반복하면서 점점 더 깊이 있는 이해를 쌓아가야만 한다.

데브옵스 · 인프라워밍업클럽4기데브옵스

dohi

워밍업 클럽 4기 - 백엔드 4주차 회고

4주차 회고강의수강Mock을 마주하는 자세Mockito로 Stubbing하기Test Double@Mock, @Spy, InjectMocksBDDMockitoClassicist VS Mockist키워드 정리더 나은 테스트를 작성하기 위한 구체적 조언한 문단에 한 주제완벽하게 제어하기테스트 환경의 독립성 보장하자테스트 간 독립성을 보장하자한 눈에 들어오는 Test Fixture 구성Text Fixture 클렌징@ParameterizedTest@DynamicTest 테스트 수행도 비용이다. 환경 통합하기Q&A키워드정리 Appendix학습 테스트Spring REST DocsOutro테스트를 작성하는 마음가짐미션Day 16https://www.inflearn.com/blogs/11176Day 18https://inf.run/S7UnS 회고이번 너무 많고 좋은정보가 있어 직접적인 강의에 대한 모든 흐름이 느껴져서회고에 자세하게 적어보려합니다.저에게 테스트는 귀찮지만 한번 만들어놓으면 계속쓰는 도구 그이상그이하가 아니였습니다.테스트 코드를 배우고 아 테스트과정에서 계속반복되면 이게 문제겠구나(메일보내기 및 알람보내기)아! 이건 테스트는 한번만해도되는데그런관점을 배우게 되었고테스트 코드를 오히려 설계할때 어떤 관점과 어떤 중요한점 마음가짐을 더알려주는 강의였습니다.사실 사수에게 배울 수 없는 관점을 더 알려주셨고실제 라이브에서도 저런생각을 하면서 코드를 짜는구나제 코드를 바라보면서 문제점과 부족한 점이 점점 보이기 시작했습니다.사실 코드에는 정답이 없다고는 하지만맨날 제가 보기 좋은 코드만 짠 것같았고테스트를 작성하면서 실제 배운관점을 프로젝트에 적용하면서 도움이되었습니다.이번에 한이유는 실제로 사용하고 바로바로 배우기위해서 시작한거였는데많은 관점을 배웠고 실제 프로젝트에서도 적용하면서 도움이 많이 되었습니다.그동안에 매번 다보기전에 정리만 하고 마무리 안보고 다음강의로 넘어가느라 빠르게 넘어간것들 다시 정리해야겠어요지하철에서 보고 영감받으면 정리하고 넘어가고 하다보니 넘어간게 엄청많았더라고요 핫그만큼 강의 사이사이에 엄청 중요한게 많고 영감이 많아서 늘 도움이 된게 많았어요 감사합니다 일단 다봤지만 체크가안된건 오늘 쓰윽 마지막까지 봐야겠어요

백엔드백엔드워밍업클럽4기워밍업클럽

박준형

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

4주차에 배운 것들📌Test Double"Test Double"은 테스트 중에 실제 객체를 대체하기 위해 사용하는 객체를 통칭하는 개념으로 다섯 가지 유형이 존재Dummy : 아무 것도 하지 않는 깡통 객체Fake : 단순한 형태로 동일한 기능은 수행하나, 프로덕션에서 쓰기에는 부족한 객체ex. FakeRepository Stub : 테스트에서 요청한 것에 대해 미리 준비한 결과를 제공하는 객체 그 외에는 응답하지 않는다.상태 검증(State Verification) : 메서드 호출 후 Stub이 상태가 어떻게 바뀌었는지 검증Mock : 행위에 대한 기대를 명세하고, 그에 따라 동작하도록 만들어진 객체행위 검증(Behavior Verification) : 메서드에 파라미터를 넘겨줬을 때, 어떤 값을 반환할지 행위를 검증Spy : Stub이면서 호출된 내용을 기록하여 보여줄 수 있는 객체 일부는 실제 객체처럼 동작시키고 일부만 Stubbing할 수 있다. 📌 나는 Classicist 일까 Mockist 일까"Classicist VS. Mockist" 강의를 듣기 전까지 Mockist였다. "Mocking을 통해서 단위 테스트로 기능을 테스트했으면 통합 테스트에서는 안해도 될 것 같은데?"처럼 단순하게 생각했다. 그런데 코치님께서 Classicist를 지향하시는 이유를 듣고난 후 다시 생각해보았다. 어쩔 수 없이 Mocking을 해야하는 상황을 제외하고, 각 기능들이 의존 관계를 가질 때는 어떤 예외가 발생할지 모르기 때문에 최대한 프로덕션 환경과 비슷하게 테스트를 하는 것이 좋다고 생각되었다. 그래서 앞으로는 Classicist 방식으로 테스트 하는 것을 지향해야겠다. 📌 그렇다면 Mocking은 언제?💡 Mocking이 유용한 상황은 대표적으로 2가지가 있다.외부 시스템에 의존하는 기능외부 시스템 같이 오류가 발생해도 우리가 통제할 수 없다.테스트할 때마다 비용이 발생하는 기능 ex) 메일 전송 ❓ 만약 한 객체에 특정 기능은 Mocking을 하고싶고, 실제 동작도 테스트하고 싶다면?이럴 땐 Spy 객체를 활용할 수 있다! Mocking을 원하는 특정 기능만 Stubbing을 하고, 나머지는 원본 객체처럼 실제 동작을 테스트할 수 있다.🔗 Mock 관련 어노테이션 정리 📌 Mockito도 BDD 스타일로Mockito.when(mailSendClient.sendEmail(anyString(), anyString())) .thenReturn(true); BDDMockito.given(mailSendClient.sendEmail(anyString(), anyString())) .willReturn(true);기존 Mockito에서는 stubbing을 위한 기능의 메서드명이 when 인데, stubbing 과정은 given 절에서 해야하는 작업이라 메서드명이 부자연스러웠다. 그럴땐 Mockito를 한 번 감싼 BDDMockito를 사용하면 BDD 스타일에 더 자연스럽게 코드를 작성할 수 있다. BDDMockito에서 given 은 Mockito의 when 과 동일한 기능을 한다. 📌한 문단에 한 주제@Test @DisplayName("상품 타입이 재고 관련 타입인지를 체크한다.") void containsStockTypeEx() { // given ProductType[] productTypes = ProductType.values(); for (ProductType productType : productTypes) { if(productType == ProductType.HANDMADE) { // when boolean result = ProductType.containsStockType(productType); // then assertThat(result).isFalse(); } if(productType == ProductType.BAKERY || productType == ProductType.BOTTLE) { // when boolean result = ProductType.containsStockType(productType); // then assertThat(result).isTrue(); } } }각 타입들을 모두 검증할 수 있도록 하나의 테스트로 구성하는 것은 단점이 있다.한 문단에 두 가지 이상의 내용이 포함된다.반복문 자체가 여러 논리 구조가 들어가게 하고, 보는 사람의 사고를 복잡하게 한다.💡이럴 경우에는 케이스를 나눠서 테스트를 작성하는 것이 좋고, 이 방식은 지양하자! 📌테스트 환경의 독립성을 보장하자다른 API를 끌고와서 테스트 간 결합도가 생길 수 있다.// given Stock stock1 = Stock.create("001", 2); Stock stock2 = Stock.create("002", 2); stock1.deductQuantity(3); // 예외 : 차감할 재고 상품이 없습니다.테스트 주제와 맞지 않은 given 절에서 예외가 발생하고 있음왜 실패했는지 유추하기 어려운 상황이 발생💡 독립성을 보장하는 방법given 절에서 순수 builder 또는 생성자 기반으로 데이터를 생성팩토리 메서드나 메서드 기반은 지양어떠한 의도를 가지고 필요한 인자만 받거나 내부에 검증 로직이 포함되어 있을 수 있기 때문 📌Test Fixture 구성하기✅Test Fixture : given 절에서 생성한 객체들Fixture : 고정물, 고정되어 있는 물체테스트를 위해 원하는 상태로 고정시킨 일련의 객체💡 공통된 Fixture들을 만들어야 할 때문서로서의 역할을 위해서 given 절에서 생성하는 것이 좋다!@BeforeEach void setUp() { // before method }@BeforeEach 를 사용공유 변수 같이 테스트 간 독립성을 훼손(테스트 간 결합도가 생김)상위 setUp 메서드에서 어떤 데이터를 생성하는지 확인해야 하며 파편화를 유발문서로서의 역할을 할 수 없음 만약 각 테스트 입장에서 봤을 때 아예 몰라도 테스트 내용을 이해하는 데에 문제가 없는 경우나 해당 공유 변수를 수정해도 모든 테스트에 영향을 주지 않는 경우는 사용해도 괜찮다.data.sql을 사용하여 테스트 데이터 생성파편화가 발생 (given 절에 데이터가 없으므로 어떤 데이터로 테스트를 하는지 확인하기 불편함)테스트가 커질 수록 또 다른 관리 포인트가 됨builder를 private 메서드로 구성파라미터에 해당 테스트에서 필요한 데이터만 받도록 구성테스트 데이터용 builder를 한 곳에 모아서 사용자바 특성 상 필요한 파라미터가 달라 새로운 builder가 계속 생겨 오히려 복잡도가 상승🚨Test Fixture 클렌징 시 유의 사항deleteAllInBatch()테이블 전체를 벌크성으로 빠르게 삭제할 수 있음외래키 제약 조건로 인해 순서를 잘 고려해야함여러 트랜잭션 경계가 참여하는 @Transactional 의 롤백 기능을 사용하기 어려워 해당 방법을 사용해야 함 deleteAll()해당 테이블을 조회하여 엔티티를 통해 delete(엔티티) 방식으로 데이터를 삭제데이터 개수만큼 쿼리가 발생 → 병목지점 유발자신과 외래키 제약 조건으로 연관된 데이터도 조회하여 삭제 📌@ParameterizedTest여러가지 값에 대한 테스트를 하나의 테스트 코드에 녹이기 위한 방법// Case 1 @ParameterizedTest @CsvSource({"HANDMADE, false", "BOTTLE, true", "BAKERY, true"}) @DisplayName("상품 타입이 재고 관련 타입인지를 체크한다.") void containsStockType4(ProductType productType, boolean expected) { // when boolean result = ProductType.containsStockType(productType); // then assertThat(result).isEqualTo(expected); } // Case 2 private static Stream<Arguments> provideProductTypesForCheckingStockType() { return Stream.of( Arguments.of(ProductType.HANDMADE, false), Arguments.of(ProductType.BOTTLE, true), Arguments.of(ProductType.BAKERY, true) ); } @ParameterizedTest @MethodSource("provideProductTypesForCheckingStockType") @DisplayName("상품 타입이 재고 관련 타입인지를 체크한다.") void containsStockType5(ProductType productType, boolean expected) { // when boolean result = ProductType.containsStockType(productType); // then assertThat(result).isEqualTo(expected); } 📌@DynamicTest환경을 설정하고, 환경에 변화를 중간 중간 검증을 하는 시나리오를 테스트하고싶을 때 사용@TestFactory @DisplayName("재고 차감 시나리오") Collection<DynamicTest> stockDeductionDynamicTest() { // given Stock stock = Stock.create("001", 1); return List.of( DynamicTest.dynamicTest("재고를 주어진 개수만큼 차감할 수 있다.", () -> { // given int quantity = 1; // when stock.deductQuantity(quantity); // then assertThat(stock.getQuantity()).isZero(); }), DynamicTest.dynamicTest("재고보다 많은 수의 수량으로 차감 시도하는 경우 예외가 발생한다.", () -> { // given int quantity = 1; // when // then assertThatThrownBy(() -> stock.deductQuantity(quantity)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("차감할 재고 상품이 없습니다."); }) ); }💡 List.of() 에 정의된 순서대로 테스트가 동작하며 이전 테스트의 형태를 그대로 유지하기 때문에 테스트 간 결합이 생겨 시나리오를 구성하여 테스트가 가능하다. 📌 테스트 환경 통합하기테스트 환경 통합전에는 전체 테스트(gradle → Tasks → verification → test) 수행 시 총 6번의 스프링 컨텍스트가 로딩되었다. 테스트가 많아질 수록 이는 더 많은 시간을 소모하게 되고, 테스트를 하지 않게 할 수 있다.@ActiveProfiles("test") @SpringBootTest public abstract class IntegrationTestSupport { @MockitoBean protected MailSendClient mailSendClient; } // 통합이 필요한 클래스에서 상속하여 환경 통합 class OrderServiceTest extends IntegrationTestSupportMocking을 하고 있는 클래스를 통합하려면 상위 클래스에 해당 객체를 옮겨주어야 한다. Mock객체가 없는 클래스와 있는 클래스는 다른 환경이라고 인식하여 제대로 통합이 이루어지지 않기 때문이다.이럴 경우 Mock이 필요없는 클래스에도 객체가 들어가는데 이게 싫다면, 테스트를 두 가지 환경으로 분리하여 Mock이 필요한 클래스, 필요하지 않은 클래스로 나누고 필요한 클래스의 상위 클래스에 Mock 객체를 몰아 넣는 형식으로 구성할 수 있다.@WebMvcTest(controllers = { OrderController.class, ProductController.class }) public abstract class ControllerTestSupport { @Autowired protected MockMvc mockMvc; @Autowired protected ObjectMapper objectMapper; @MockitoBean protected OrderService orderService; @MockitoBean protected ProductService productService; } class ProductControllerTest extends ControllerTestSupportController 테스트 환경 통합도 동일하게 진행하면 된다.✅환경을 통합한 후에는 총 2번의 스프링 컨텍스트가 로딩되어 테스트 수행 시간을 단축할 수 있었다. 📌Q. private 메서드의 테스트는 어떻게 하나요?private 메서드는 테스트할 필요가 없다!만약 private 메서드를 테스트 하고 싶은 때에는 “객체를 분리할 시점인가?”에 대해서 생각해봐야 한다.public 메서드(공개 API)를 가지고 있다는 것은 이를 의존하는 테스트코드, 컨트롤러 즉 클라이언트 입장에서는 공개 메서드만 알면 된다. 외부로 노출되지 않는 private 메서드에 대해서는 알 필요가 없다는 것이다. public 메서드의 테스트를 수행하면서 private 메서드의 테스트도 저절로 수행되기 때문이다.해당 private 메서드의 테스트는 꼭 필요하다고 생각되면 객체에 역할에 맞게 분리가 되지 않았을 수 있다. 그러므로 객체를 분리하여 다른 클래스로 만든 뒤 테스트를 구성하는 것이 바람직하다. 📌Q. 테스트에서만 필요한 메서드가 생겼는데 프로덕션 코드에서는 필요 없다면?만들어도 된다! 하지만 보수적으로 접근해야 한다.@Test @DisplayName("신규 상품을 등록한다.") void createProduct() throws Exception { // given ProductCreateRequest request = ProductCreateRequest.builder() .type(ProductType.HANDMADE) .sellingStatus(ProductSellingStatus.SELLING) .name("아메리카노") .price(4000) .build(); }해당 request 객체는 프로덕션 코드에서는 필요가 없다.(@RequestBody 로 외부로부터 값을 받기 때문) 하지만 테스트를 하기 위해서는 꼭 필요하다.객체로서 가져도 되는 기능(빌더, 생성자, 사이즈)이면서 미래에도 충분히 사용이 될 수 있는 기능이라면 만들어도 된다. 📌 학습 테스트?잘 모르는 기능, 라이브러리, 프레임워크를 학습하기 위해 작성하는 테스트여러 테스트 케이스를 스스로 정의하고 검증하는 과정을 통해 보다 구체적인 동작과 기능을 학습할 수 있다.관련 문서만 읽는 것보다 훨씬 재미있게 학습할 수 있다.💡사용하려는 라이브러리를 테스트 코드를 작성하면서 학습하면 팀 내에서도 공유하는 자료로도 활용할 수 있다. 📌 Spring REST Docs테스트 코드를 통한 API 문서 자동화 도구API 명세를 문서로 만들고 외부에 제공함으로써 협업을 원활하게 한다.기본적으로 AsciiDoc을 사용하여 문서를 작성한다.👍장점테스트를 통과해야 문서가 만들어진다. (신뢰도가 높다.)프로덕션 코드에 비침투적이다.👎단점코드 양이 많다.설정이 어렵다.지금까지 API 문서를 위해 Swagger를 사용했는데, Swagger는 어노테이션으로 선언만 해주면 해당 API가 검증되지 않아도, 해당 API의 명세가 생성된다. 그에 반해 Spring REST Docs는 테스트 코드가 통과해야 명세가 생성되므로 안정성을 보장하는 면에서 더 이점이 있다고 생각한다. 그렇지만 명세를 위한 코드의 양이 너무 많고, 작성하기 번거로웠다. 이 문제를 해결하기 위해 자동화할 수 있는 방법을 찾아봐야겠다.미션을 통해 배운 것들📌 Day 16각 레이어가 어떤 특징들을 가지는지 명확하게 알 수 있었고, 적합한 테스트 방법과 Classicist인지 Mockist인지 생각할 수 있는 기회를 준 좋은 미션이였습니다!해당 미션을 수행할 때까지는 Business Layer에서 통합 테스트를 수행 시 Persistence Layer의 기능은 단위 테스트로 검증했으니 Mocking하여 비즈니스 로직만 테스트하는 것이 좋다고 생각했는데, 이후 "Classicist VS. Mockist" 강의를 듣고 해당 부분에 대한 제 생각을 다시 정리할 수 있었습니다. 📌 Day 18다른 강의에서 Mock 관련 기능들을 간단히 배웠는데, 그 때까지는 Mocking을 한다는 것이 정확히 뭔지 잘 이해하지 못했습니다. 그리고 비슷한 어노테이션과 서로 다른 주입법들로 인해서 복잡하고 이해하기 힘들었습니다.이번 강의를 통해 "Mocking을 왜 해야하는지"와 각 어노테이션들을 사용하는 방법과 상황에 대해서 완벽하게 이해할 수 있었습니다. 미션을 통해 같은 Mock 객체를 생성할 때도 @Mock 을 사용해야할 때, @MockitoBean 을 사용해야할 때를 명확하게 알 수 있었고, setUp절에 배치하면 좋은 Fixture 들을 구분하는 방법도 이해할 수 있었습니다.

백엔드워밍업클럽4기백엔드4주차발자국

dohi

워밍업 클럽 4기 - 백엔드 Day 18

과제 Day181. Mockito 주요 어노테이션 정리 (@Mock, @MockBean, @Spy, @SpyBean, @InjectMocks)@Mock순수 Mockito 기반 Mock 객체 생성. 실제 동작 없이 필요한 부분만 Stubbing 가능. Spring Context와 무관@MockBeanSpring Context에 등록되는 Mock 객체.기존 Bean을 대체함. @SpringBootTest, @WebMvcTest 등에서 사용.@Spy실제 객체를 감싸서 일부만 Stubbing 가능. 상태 확인이나 부분 동작 제어 시 유용. Mockito 단독 사용 가능.@SpyBeanSpring Context에 등록된 Bean을 Spy로 교체.실제 동작은 유지하면서 일부만 Stubbing.@InjectMocks@Mock 또는 @Spy 객체를 테스트 대상 클래스에 주입. 직접 객체 생성 없이 의존성 자동 주입을 지원.요약 Spring 없이 단순 로직 테스트: @Mock, @InjectMocks, @SpySpring 환경 테스트: @MockBean, @SpyBean실제 동작을 살리되 일부만 조작하고 싶을 때: @Spy, @SpyBean전체를 가짜 객체로 대체할 때: @Mock, @MockBean2. 테스트의 각 항목을 @BeforeEach, given절, when절에 배치AS-IS@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 검증 } TO-BE@BeforeEach void setUp() { 0-1. 사용자 생성에 필요한 내용 준비 (1-1, 2-1, 3-1) 0-2. 사용자 생성 (1-2, 2-2, 3-2) 0-3. 게시물 생성에 필요한 내용 준비 (1-3, 2-3, 3-5) 0-4. 게시물 생성 (1-4, 2-4, 3-6) } @DisplayName("사용자가 댓글을 작성할 수 있다.") @Test void writeComment() { // given 1-5. 댓글 생성에 필요한 내용 준비 // when 1-6. 댓글 생성 // then 검증 } @DisplayName("사용자가 댓글을 수정할 수 있다.") @Test void updateComment() { // given 2-1. 댓글 생성에 필요한 내용 준비 (2-5) 2-2. 댓글 생성 (2-6) // when 2-3. 댓글 수정 (2-7) // then 검증 } @DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.") @Test void cannotUpdateCommentWhenUserIsNotWriter() { // given 3-1. 사용자2 생성에 필요한 내용 준비(3-3) 3-2. 사용자2 생성(3-4) 3-3. 사용자1의 댓글 생성에 필요한 내용 준비(3-7) 3-4. 사용자1의 댓글 생성(3-8) // when 3-5. 사용자2가 사용자1의 댓글 수정 시도 (3-9) // then 검증 }작업.공통으로 사용하는 게시판과 사용자(1)은 Setup댓글도 공통으로 사용한다라고 볼 수 있지만 writeComment의 테스트에서 When에 해당하여 제외그외 when에 기준으로 필요한 데이터 생성은 given에 배치

백엔드워밍업클럽4기워밍업클럽백엔드

dohi

워밍업 클럽 4기 - 백엔드 Day 4

수정내역썸네일 추가과제1. 읽기 좋은 코드로 리팩토링해 보기AS-ISpublic boolean validateOrder(Order order) { if (order.getItems().size() == 0) { log.info("주문 항목이 없습니다."); return false; } else { if (order.getTotalPrice() > 0) { if (!order.hasCustomerInfo()) { log.info("사용자 정보가 없습니다."); return false; } else { return true; } } else if (!(order.getTotalPrice() > 0)) { log.info("올바르지 않은 총 가격입니다."); return false; } } return true; }  TO-BEEarly Return 적용if else 문에 대한 조건에 대하여 if문 별 return이 존재하다는걸 파악하였고 조건에 맞게 Early Return적용 order가 null일 경우 추가 적용공백 라인 적용if문에 대한 공백 라인을 적용함으로서 코드의 분기점을 명확하게 구분부정연산자를 메서드안에 포함하여 메서드명으로 확인할 수 있도록 변경!(order.getTotalPrice() > 0), !order.hasCustomerInfo() 의 가독성 향상을 위한 메서드화 예외 처리return false; 에 대해서 각 메서드에서 IllegalArgumentException 예외를 던지도록 수정하였고예외처리를 통해 log를 try-catch문으로 한번에 처리하도록 수정(1번버전) 1. 예외처리 버전 ( 다소 엄격 )public boolean validateOrder(Order order) { if (order == null) { log.info("Order 객체가 null입니다."); return false; } try{ validateOrderItemsExist(order); validateTotalPrice(order); validateCustomerInfoPresent(order); return true; }catch(IllegalArgumentException e) { log.info(e.getMessage()); return false; } } public void validateOrderItemsExist(Order order) { if (order.getItems().size() == 0) { //order.getItems().isEmpty()도 고려 throw new IllegalArgumentException("주문 항목이 없습니다."); } } public void validateTotalPrice(Order order){ if((order.getTotalPrice() <= 0)){ throw new IllegalArgumentException("올바르지 않은 총 가격입니다."); } } public void validateCustomerInfoPresent(Order order) { if (!order.hasCustomerInfo()) { throw new IllegalArgumentException("주문에 사용자 정보가 필요합니다."); } } 2. 예외처리 미적용public boolean validateOrder(Order order) { if (isOrderNull(order)) return false; if (isOrderItemsEmpty(order)) return false; if (isTotalPriceInvalid(order)) return false; if (isCustomerInfoMissing(order)) return false; return true; } public boolean isOrderNull(Order order) { if (order == null) { log.info("Order 객체가 null입니다."); return true; } return false; } public boolean isOrderItemsEmpty(Order order) { if (order.getItems().size() == 0) { //order.getItems().isEmpty()도 고려 log.info("주문 항목이 없습니다."); return true; } return false; } public boolean isTotalPriceInvalid(Order order) { if((order.getTotalPrice() <= 0)){ log.info("올바르지 않은 총 가격입니다."); return true; } return false; } public boolean isCustomerInfoMissing(Order order) { if (!order.hasCustomerInfo()) { log.info("사용자 정보가 없습니다."); return true; } return false; }  2. SOLID에 대하여 자기만의 언어로 정리해 봅시다. 1.  SRP - Single Responsibility Principle(단일 책임 원칙)한 클래스는 딱 하나의 역할만 하자.하나의 클래스가 너무 많은 일을 하려고 하지 말자.  2.  OCP- Open/Closed Principle(개방/폐쇄 원칙)기존 코드를 고치지 않고, 기능을 확장할 수 있어야 한다.수정에는 닫혀 있고, 확장에는 열려 있어야 한다.조건문을 늘리기보다는 새로운 클래스를 추가해 기능을 확장하자. 3.  LSP - Liskov Substitution Principle(리스코프 치환 원칙)부모 타입을 사용하는 곳에 자식 타입을 써도 제대로 작동해야 한다.부모 클래스의 역할을 자식이 완전히 대체할 수 있어야 한다.자식 클래스가 부모의 약속을 깨면 안 된다.  4. ISP - Interface Segregation Principle(인터페이스 분리 원칙)쓸데없는 인터페이스는 나눠서 큰 종합세트를 주지말고 작은 인터페이스 여러개가 좋다클라이언트가 사용하지 않는 메서드에 의존하게 하지 말자.하나의 거대한 인터페이스보다는, 작은 인터페이스 여러 개가 좋다.  5. DIP - Dependency Inversion Principle(의존 역전 원칙)고수준 모듈은 저수준 모듈에 의존하지 말고, 추상(인터페이스)에 의존하자.핵심 로직(고수준)이 세부 구현(저수준)에 끌려다니면 유연성이 떨어진다.인터페이스를 사이에 두고 서로 느슨하게 연결하자.   강의Readable Code: 읽기 좋은 코드를 작성하는 사고법

백엔드백엔드워밍업클럽4기워밍업클럽

dohi

워밍업 클럽 4기 - 백엔드 Day 16

수정내역썸네일 추가과제1. Persistence Layer데이터를 읽고 쓰는 기능, 즉 DB와 직접 연결되는 창구 역할을 합니다.데이터 저장, 조회, 수정, 삭제 같은 순수한 CRUD 작업만 담당해야 하며, 복잡한 계산이나 로직은 포함되면 안 됩니다.어떻게 테스트?JPA 기반의 Repository를 중심으로 테스트합니다.@ActiveProfiles를 사용해 테스트 전용 DB를 따로 두어 테스트실제 데이터를 저장하고 불러오면서 동작을 검증합니다.예: 특정 조건으로 조회 시 원하는 값이 정확히 나오는지 확인 @DataJpaTest 사용시 자동으로 롤백 (@Transactional)포인트빠르게 테스트하고 싶으면 @DataJpaTest순수하게 CRUD에 집중해야 하므로, 로직이 들어있다면 오히려 잘못된 구조일 수 있음2. Business Layer비즈니스 로직을 담는 곳입니다.예를 들어 “결제를 하면 재고가 줄어야 한다” 같은 업무 흐름을 구현하는 레이어며트랜잭션 보장과 동시성을 고민해야한다.어떻게 테스트?@SpringBootTest 또는 @ServiceTest 등으로 전체 흐름을 확인할 수 있는 환경 구성필요한 데이터는 테스트 전 직접 저장하거나 Mock 처리경계값 테스트given-when-then 구조로 시나리오 기반 테스트 작성포인트이 레이어는 트랜잭션이 중요한 레이어이므로, 롤백 여부도 테스트 대상핵심은 “이 비즈니스 흐름이 내가 기대한 대로 잘 작동하느냐”임3. Presentation Layer웹 요청(HTTP 등)을 처음으로 받아들이는 입구입니다.컨트롤러에서 파라미터를 검증하고, 서비스에 요청을 위임하며, 응답을 구성합니다.즉, 입력과 출력의 인터페이스 역할을 합니다.어떻게 테스트?단위 테스트처럼 접근컨트롤러만 띄우고, 내부 서비스나 레포지토리는 Mock 처리MockMvc, @WebMvcTest 등을 이용해 REST API 요청/응답을 검증validation, 상태 코드, JSON 응답 구조를 확인포인트복잡한 로직보다 “입력값에 따라 어떤 응답이 나오는가”에 집중실제 호출 흐름이 아니라 API 인터페이스가 잘 동작하는지만 보자과제 회고각 레이어마다 역할이 다르기 때문에,그에 맞는 테스트 전략과 도구도 달라져야 한다는 걸 이번에 확실히 느꼈습니다.Persistence는 DB를 잘 다루는지Business는 로직 흐름이 맞는지Presentation은 입력과 출력이 잘 연결되는지

워밍업클럽워밍업클럽4기백엔드

양파쿵야

워밍업클럽4기 DevOps 4주차 발자국

Helm과 Kustomize 비교하며 사용-2Helm과 Kustomize는 무엇이 더 좋다기보단, 서로 지향하는 방향이 극명히 다른 것 같다. 대규모 배포를 사용하게 될지 아직은 잘 모르겠어서... 약간은 더 직관적으로 느껴지는 Kustomizee부터 사용하게 될 것 같다.최근 보안과 관련해 안좋은 소식들이 많이 있었기에, 도커 허브와 쿠버네티스 클러스터에 대한 접근 제어를 강화하는 방법을 실습할 수 있어서 좋았다.컴퓨터가 좋지 않아 빌드를 계속한다.... 10분이 넘어도 멈추지 않는다.... ArgoCD 빠르게 레벨업-1ArgoCD가 Git을 필요로 하는 만큼, 불필요한 업데이트가 발생하지 않도록 Git 레포지토리를 분리해서 관리하는게 정말 중요할 것 같다.Jenkins Pipeline과 비교해 배포가 간단하고, 무엇보다 쿠버네티스와 동기화되어 배포 상태를 그래픽으로 한번에 조회할 수 있어서 좋다.로그도 Jenkins는 각 파이프마다 클릭해서 페이지가 이동되고 봐야했는데, ArgoCD는 실시간으로 Pod 로그도 볼 수 있어서 매우 편리했다. ArgoCD 빠르게 레벨업-2ArgoCD Image Update를 사용해야하는 이유를 처음에 이해하지 못했는데 3번 구간 반복하니 이해가 되었다.Git과 완전히 동일한 환경을 구현하고자 할 때 도움되는 옵션도 알게되어, 테스트 환경을 만들 때 유용하게 사용할 수 있을 것 같다.다만... 컨테이너 빌드를 완료했는데 감지하는데 실패했다...(컴퓨터가 너무 느림) - 그치만 미션에선 이미지 변경 감지 성공! ArgoCD 빠르게 레벨업-3CLI를 사용해 모니터링할 수 있다니 ArgoCD는 정말 완벽한 것 같아요YAML 파일의 구성도 기존 쿠버네티스 환경과 호환되도록 세심하게 배려한 게 정말 잘 만든 솔루션이라고 느낍니다Jenkins를 사용해서 Blue/Green과 Canary 배포를 진행할 때는 이론은 쉽지만 막상 스크립도 구현하는 것은 까다로운 배포 작업이라고 생각된 것에 반해, ArgoCD는 정말 딸깍으로 배포할 수 있는 강력한 솔류션 같습니다.

데브옵스 · 인프라워밍업클럽4기

양파쿵야

쿠버네티스 어나더 클래스 지상편: Sprint2 Day17 ArgoCD 3

Argo RolloutsArgoCD 없이도 Rollouts(Blue/Green, Canary) 배포가 가능하다.ArgoCD와 독립적인 솔루션이며, ArgoCD와 함께 사용하면 ArgoCD 대시보드에서 Rollouts 버튼이 생긴다. Blue/Green 배포운영 환경에서만 테스트가 가능한 환경에서 주로 사용하는 배포 전략이다.배포 시 롤백이 빠르다.배포 중 V1 과 V2 간의 동시 호출이 발생하지 않는다.Script를 사용해 자동 배포가 가능하다.V2에 과도한 트래픽이 유입될 경우 문제가 발생할 수 있다. Rollouts을 사용한 Blue/Green 배포 과정Rollout 컨테이너 → Service 2개 지정: Active(서비스 사용자 접근), Preview(V2로만 접근) V1(Blue) 배포Rollout → Replica Set V1 → Pod V1 ← Active / Preview Service배포 이전에는 2개 서비스 모두 Blue를 바라본다 V2(Green) 배포Rollout → Replica Set V1 → Pod V1 ← Active ServiceRollout → Replica Set V2 → Pod V2 ←Preview Service ← QA담당자배포 이후에 Preview Service만 Green을 바라본다. PromotePod V1 삭제 ← Promote → Active / Preview Service → Pod V2Promote를 진행하면 Blue는 제거한다 Canary 배포특정 헤더 값에 한해 트래픽이 유입되도록 할 수 있다.서서히 트래픽을 전환하므로 콜드 스타트를 방지할 수 있다.업그레이드 목적 이외에도 두 버전을 비교하기 위한 A/B 테스트에도 사용된다. Rollouts을 사용한 Canary 배포 과정Blue/Green처럼 새 서비스를 만들지 않아도 트래픽을 조절할 수 있다. V1 배포Rollout → Replica Set V1 → Pod V1 ← Service V2(Canary) 배포Rollout → Replica Set V1 → Pod V1 ← ServiceRollout → Replica Set V2 → Canary Pod ← Service Promote순차적으로 Cananry Pod의 비중을 늘림setWeight: 전체 Pod에서 Canary가 차지하는 비중pause: {} : Promote가 올 때까지 무한정 대기 5. Argo Rollouts를 이용한 Blue-Green 배포 - 22335-2. App 생성 하기 - [+ NEW APP] 클릭5-3. 배포하기 - [SYNC] 클릭 > [SYNCHRONIZE] 클릭5-4. 배포 확인배포 완료 5-5. 트래픽 보내기 5-6. ArgoCD UI에서 Blue/Green 배포 시작하기rollout.yaml spec: replicas: 2 strategy: blueGreen: activeService: api-tester-2233-active previewService: api-tester-2233-preview autoPromotionEnabled: falseRollout을 사용하기 위해 배워야할 부분은 spec - strategy - blueGreen 뿐 autoPromotionEnabled: falsetrue일 경우 Green 파드가 정상 작동하면 Promote가 자동으로 반영된다. 5-6-3. Git에서 image의 tag 변경5-6-4. ArgoCD에서 Applications > api-tester-2233 > [SYNC] 클릭Git의 수정 사항을 감지함 5-6-5. 트래픽 확인rollouts-pod-template-hash 를 사용해 트래픽을 전환할 수 있다. 5-6-6. Promote 진행Rollout 대시보드는 기본적으로 안만들어짐Rollback: Green 파드 삭제Restart: Blue/Green 파드 모두 재시작, 원하는 파드를 K8s에서 삭제하여 재시작하는 방법이 더 좋음Promote: 다음 단계PromoteFull: 배포 완료 Promote를 진행한다. 5-6-7. 트래픽 확인 5-7. Rollout CLI로 조회 해보기배포 상황을 모니터링해줌 6. Argo Rollouts를 이용한 Carary 배포 - 22346-1. Master에서 Kubectl로 Rollouts 배포하기 6-2. 배포 모니터링6-3. 트래픽 보내기 (1.0.0 App 연결) 6-5. Argo Rollouts CLI로 Canary 배포 시작하기 (1.0.0 -> 2.0.0로 변경)Canary Pod가 만들어졌다. 6-7. 트래픽 확인 (1.0.0 App 연결, 2.0.0 App 연결)트래픽이 Canary로 분산되고 있다. Promote를 진행해 다음 단계로 넘어간다. 전체 Pod중 66%의 Canary Pod가 생성되었다. 6-8. 트래픽 확인 (2.0.0 App 연결) 배포가 모두 완료되었다!  6-9. 리소스 정리하기     

데브옵스 · 인프라워밍업클럽4기

양파쿵야

쿠버네티스 어나더 클래스 지상편: Sprint2 Day16 ArgoCD 2

DevOps 엔지니어가 배포를 해야되는 상황DevOps 엔지니어는 리소스 스펙 변경이 필요할 때, App 버전이 업그레이드 될 때 컨테이너 이미지를 변경해 배포를 해야 한다.원래 DevOps 엔지니어는 App 버전을 교체할 때 YAML 파일을 수정해야하지만, Helm을 사용하면 배포 명령에 이미지 태그를 동적으로 부여하므로 YAML 파일 수정 없이 자동 배포가 가능하다. ArgoCD Image Updater를 사용해야 하는 이유ArgoCD가 이미지 변경을 감지하여 자동으로 K8s로 배포한다.다만 Jenkins가 아닌 ArgoCD가 감지하여 배포하므로, 컨테이너 빌드가 끝난 이후에 자동 배포가 어려워졌다. → Jenkins가 YAML파일을 수정하고 Git에 업그레이드 하는 방법은 로직이 복잡해진다.⇒ ArgoCD Image Update가 도커 허브를 모니터링하고 이미지 업데이트가 감지되면 ArgoCD에 배포 명령을 전송, K8s로 자동 배포를 수행한다. ArgoCD Image Update 요구사항ArgoCD Image Update는 내부적으로 --set image.tag 명령을 사용하기 때문에 Helm, Kustomize 배포 시에만 사용 가능하다.Image Update와 도커 허브 연결 설정이 필요하다.배포시 태그 규칙 추가가 필요하다. 4. Argo Image Updater 를 이용한 이미지 자동 배포 (helm) - 22324-1. Image Updater values-dev.yaml 파일 확인4-3. Jenkins에서 배포4-4. Image Updater 동작 확인설정한 로그가 없어 정보가 결과가 없음. 각 이미지마다 alias를 설정해야한다. Git에서 삭제하더라도 K8s에서 리소스를 유지할 지 여부, 내용 출돌시 Git의 변경을 자동으로 반영할 지 여부: Git과 항상 동일한 형상을 유지하고 싶을 때 사용할 수 있다. → K8s의 오토스케일링이 발생해도 Git과 형상을 유지하기 위해 다시 돌아간다.(강의 영상처럼 분명 SYNC 켰는데 들어가보면 꺼져있음) 4-6. 도커 빌드 Job 생성 3가지 환경에 배포해봤지만 변화가 없다...

데브옵스 · 인프라워밍업클럽4기

양파쿵야

쿠버네티스 어나더 클래스 지상편: Sprint2 Day15 ArgoCD 1

ArgoCD 아키텍처ArgoCD는 K8s 전용 배포 툴이며 릴리즈 파일 저장소로 반드시 Git을 필요로 한다. 타 시스템⏬Events: 이벤트 버스 구조의 아키텍처 도구⏬Workflow: 워크플로우 매니지먼트 도구 → 받은 이벤트의 조건에 따라 실행 순서를 생성⏬CD (Image Update: 도커 컨테이너 이미지 변경을 감지)⏬Rollouts: 고급 배포 지원 → 특정 배포 전략으로 K8s 자원 생성⏬Kubernetes Git 레파지토리 분리접근 유저별 권한을 관리할 수 있고, 불필요한 코드를 다운로드 받지 않도록 방지한다.App 소스 코드 전용 - 개발자, 소스 빌드App 릴리즈 전용 - 데브옵스 엔지니어 / 개발자Addon 설치 전용 - 운영자 ArgoCD 배포의 필요 정보Application: 하나의 App을 배포하는 단위, Jenkins의 Project JobProject: Application 그룹, Default가 기본값Source: 연동할 Git 정보Destination: 연동할 K8s 클러스터 정보Refresh: 변경 사항 측정 주기Synchronize: K8s 배포 실행 주기GenernalSync Policy: 변경 사항 발생 시 자동 / 수동 배포 지정Sync Option: 배포 상세 옵션 (Namespcae 자동 생성 등)Prune Policy: 리소스 삭제 정책Desired Manifrest: Git에서 다운로드 받은 Manifest, Git에서 수정하고 Refrest 해야한다.Live Manifrest: K8s의 리소스를 조회한 Manifest → diff는 Git에 변경 사행이 반영될 Live와 현재 Live를 비교해 보여준다. ArgoCD 설치 및 배포 (kubectl, helm)  2. App 배포하기 (kubectl) - 22313. App 배포하기 (helm) - 2232Helm의 -f와 동일 

데브옵스 · 인프라워밍업클럽4기

양파쿵야

쿠버네티스 어나더 클래스 지상편: Sprint2 Day14 Helm과 Kustomize 2

Kustomize 구조Kustomize는 폴더를 수작업으로 생성해야한다.메인폴더디폴트 포맷 폴더kustomization.yaml ← 배포할 파일 선택 및 공통값 설정베이스 YAML 파일오버레이 영역 폴더배포 환경을 각 폴더로 구분해 선택적으로 배포할 수 있음kustomization.yaml ← 배포할 파일 선택 및 공통값 설정배포 환경에 맞게 오버레이 될 파일 배포할 파일 선택에 대한 차이점Helm: hpa.yml 파일에서 선언한 조건문이 values.yaml 파일에 의해 제어되어 배포됨Kustomize: kutomization.yaml 파일에 배포할 파일을 명시적으로 선언해 배포 공통값 설정에 대한 차이점Helm: YAML 템플릿 내에 다른 파일에서 변수를 주입됨Kustomize: Kustomization.yaml 파일 내 coomonLabels 키의 Lables가 모든 YAML 파일에 주입됨 환경별 설정에 대한 차이점Heml: values-dev.yaml, values-qa.yaml, values-prod.yaml 로 구별배포 명령에 -f 옵션으로 환경 파일를 선택: helm install api-tester-32222 ./app -f ./app/values-dev.yamlKustomize: 각 환경 폴더 dev, qa, prod 로 구별배포 명령에 폴더를 선택: kubectl apply -k ./app/overlays/dev  1. 다양한 배포 환경을 위한 Kustomize 배포하기 - 2222dev 개발환경이 선택되어 있다. -> Jenkins 파일 내에 있지만 처음 배포에 인식 안됨 dev 환경으로 배포 시작 실제 실무 환경에서는 K8s 네임스페이스 구분이 없어도 문제되지 않는다. 2. 다양한 배포 환경을 위한 Helm 배포하기 - 2223배포 파이프라인 구축 후 마주하게 되는 고민들컨테이너 빌드 후 도커 허브에 업로드할 때 사용되는 config.json에는 도커 허브의 사용자 정보가 저장된다. config.json 파일은 암호화가 되지 않기 때문에 인증 정보가 노출될 수 있다. Helm에서 사용하는 K8s 클러스터의 인증서는 리눅스 ROOT 권한으로 관리되어 인증서 파일을 복사하면 누구나 K8s 클러스터에 API 요청을 진행할 수 있다.⇒ CI/CD 서버의 접근 제어를 강화해야한다.⇒ 도커 허브 계정 정보와 K8s 클러스터 인증서를 Jenkins의 Credential로 등록하고 암호화하여 관리한다.⇒ 도커의 경우 Jenkins에서 암호화하더라도 config.json은 암호화되지 않는다, 배포시마다 로그인/로그아웃을 해 접속 정보를 삭제해야 한다.⇒ docker-credential-helpers는 config.json에 대한 암호화를 제공한다. 컨테이너 빌드가 지속적으로 진행되면서 CI/CD에 컨테이너 이미지가 누적된다. 서버의 디스크가 모두 사용되면 Jenkins 서비스가 중단된다.⇒ 도커 허브에 업로드 후 CI/CD 서버에 만들어진 이미지를 삭제한다. Helm을 통해 여러 App을 배포할 때 지난 실습처럼 App 하나마다 네임스페이스를 만들지 않고, 별도의 그룹 개념으로 App을 묶어 관리하는 경우가 많다.⇒ 네임스페이스는 배포와 별도로 관리하는게 좋다, Helm 앱을 삭제할 때 네임스페이스를 제거하면 네임스페이스에 포함된 리소스가 삭제되므로 Helm은 의도적으로 삭제하지 않는다. 배포 이후에 Pod가 완전히 정상적으로 기동됐는지 체크가 필요하다.⇒ Helm의 자주 쓰는 부가 기능으로 --wait 옵션을 사용한다. K8s는 Deployment의 Template 영역의 수정이 발생해야 업그레이드를 진행하므로 Helm 배포를 해도 업그레이드가 되지 않는 경우가 있다.⇒ Helm의 자주 쓰는 부가 기능으로 annotations를 사용해 새 배포시마다 랜덤 값 생성해 Helm 배포시마다 K8s가 업그레이드를 수행하도록 한다. 컨테이너 이미지의 태그 관리에서 개발환경은 잦은 배포를 진행하므로 versioning의 의마가 없지만, 검증/운영 환경은 계획된 배포를 진행해 versioning이 필수이다.⇒ 컨테이너 이미지의 버전 명명 규칙상 v를 제거하는게 맞다.⇒ 개발 환경은 이미지 태그로 latest를 사용하고, pullPolicy로 Always(항상 도커 허브에서 이미지를 가져온다)를 사용한다. 단, 인터넷 연결이 없으면 도커 허브에 접근을 못하므로 Pod 생성이 실패되고, Heml의 annotations을 사용하지 않는다면 이미지 태그가 latest로 고정되어 Template 수정이 없으므로 K8s가 업그레이드를 전혀 수행하지 못한다.⇒ 검증/운영 환경은 이미지 태그에 버전을 명시하고, pullPolicy로 IfNotPresent(K8s Node에 해당 이미지가 있으면 사용, 없으면 도커 허브에서 다운로드)를 사용한다. 운영 환경에서 Pod가 재시작되거나 스케일링이 작업 중인 상태에서 이미지를 도커 허브에서 다운로드받는 비효율적인 작업이 생략된다. 개발 환경에서 컨테이너 이미지에 대란 롤백을 요청하는 경우⇒ 개발 환경의 빌드부터 버전 관리를 시작해 개발자가 요청하는 버전으로 교체될 수 있도록 대응한다.매 배포시마다 이미지 태그가 변하므로 pullPolicy를 IfNotPresent로 변경해도 K8s에서 업그레이드를 수행할 수 있다.컨테이너 이미지를 받는 사람은 Latest가 단순 최신 개발 버전이 아닌, 운영환경까지 배포된 최신 안정화 버전으로 기대하고 다운로드 받는다.⇒ 프로젝트가 커질 수록 개발 이미지를 버전 관리하면서 바꿔가는게 좋다. CI/CD 서버와 마찬가지로 K8s 클러스터도 이미지를 계속 다운로드 받는다.⇒ Kubernetes Garbage Collector에서 사용하지 않는 이미지는 자동으로 삭제된다.  3. 배포 파이프라인 구축 후 마주하게 되는 고민들 2224 3-3-1. 중요 데이터 암호화 관리 3-3-1-1. 도커 접속 정보 및 쿠버네티스 config를 위한 New credentials 생성 ● Docker 접속정보 Credentials 등록 3-3-1-3. CI/CD Server에서 Docker Logout 및 kubeconfig 삭제 3-4. [파라미터와 함께 빌드] 실행 후 PROFILE을 [dev]로 선택하고 [빌드하기] 클릭 (빌드를 계속 하는 중,.,,,,)   

데브옵스 · 인프라워밍업클럽4기

이강호

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

👣3주 차 발자국 Practical Testing: 실용적인 테스트 가이드 ✅ 강의 요약섹션 6. Spring & JPA 기반 테스트 Layered Architecture단점 : 기술에 대한 강결합 통합테스트단위테스트 만으로는 기능 전체의 신뢰성을 보장할 수 없다. (여러 모듈 및 여러 객체가 협력하기에) 여러 모듈이 협력하는 기능을 통합적으로 검증하는 테스트Spring / JPA 훑어보기 & 기본 엔티티 설계 IoC (Inversion of Control)객체의 생성과 소멸, 생명주기 관리를 제 3자가 하는 원리DI (Dependency Injection)필요한 객체를 직접 생성하는 것이 아닌 외부에서 생성해 주입하는 것IoC이 DI로 실현된다.AOP (Aspect Oriented Programming)비즈니스 흐름과 관계없는 공통 기능 (트랜잭션, 로깅 등) 을 분리해 모듈화 시키는 것을 의미스프링에서는 Proxy형태로 제공ORM (Object-Relational Mapping)객체 지향 패러다임과 관계형 DB 패러다임의 불일치를 해결 JPA (Java persistence API)Java 진영의 ORM 기술 표준JPA - 인터페이스, 구현체 - Hibernate (대표적으로 사용됨)Spring에서는 JPA를 한번 더 추상화한 Spring Data JPA 형태로 제공주로 QueryDSL과 같이 사용됨  Persistence Layer 테스트@DataJpaTestJPA 관련 컴포넌트만 테스트할 때 사용하는 전용 어노테이션@SpringBootTest보다 가볍다.@Transactional이 있어 롤백이 된다.그치만 선호하지 않는다.Persistence LayerData access의 역할만 해야한다.Data의 CRUD에만 집중한 레이어.Business Layer 테스트비즈니스 로직을 구현하는 역할Persistence Layer와의 상호작용을 통해 개선 (통합적 테스트)트랜잭션을 보장해야 한다.@WebMvcTestPresentation Layer에 대한 단독 테스트시 사용하는 어노테이션@Transactional(readOnly = true)읽기 전용JPA에서는 스냅샷 저장, 변경감지가 일어나지 않아 성능 향상 read only값을 보고 DB의 endpoint를 분리할 수 있다. (true일 경우, read 전용 DB로 보낸다.)구분을 잘 하는 것이 중요하다.readOnly = true 구분이 누락될 수 있기에 아래 방법을 사용.service class 상단에 readOnly = true를 걸고, CUD 작업이 있다면 메서드 단위에 @Transactional을 건다.CQRS읽기(조회)와 쓰기(명령)의 책임을 분리하는 소프트웨어 아키텍처 패턴Command(CUD) 와 Query(Read) 의 책임을 분리하자. (read가 훨씬 많이 일어난다.) 성능 최적화, 비지니스 로직의 분리, 장애 격리를 할 수 있다. ✅ 강의 회고WebMvcTest의 사용과 Transactional(readOnly = true)의 중요성을 알게 되었다.그리고 @SpringBootTest는 데이터 클랜징이 필요 하기 때문에 클랜징 때문에 일어나는 문제를 유의해서 봐야하는걸 배웠다. 데이터 세팅에 손이 많이 가기 때문에 테스트가 귀찮다고 느껴지는 것이 아닌가 싶었다. ✅ 미션 과정 & 회고미션 PR - link우선 스터디 카페 이용권 선택 시스템을 기반으로 미션을 진행했다.강의 내용처럼 @DisplayName을 최대한 자세히 작성하려고 했고, 미션 조건처럼 3개 이상의 서로 다른 클래스를 만족하기 위해 기능이 다른 클래스를 선택해 작성했다.그리고, BDD (given/when/then) 스타일을 따라 작성했다.강의 내용처럼 테스트가 '성공한다' , '실패한다' 의 의미를 포함하지 않게(테스트의 현상을 포함하지 않게) DispayName을 작성하려 했고, 최대한 도메인에 관련된 이름을 넣어서 표현했다. TDD 방식으로 작성한 테스트가 아니기 때문에 해피케이스만 작성된 것 같다. 아무래도 이에대한 보완은 중간점검때 확인해 봐야 할 것 같다. 강의 - Practical Testing: 실용적인 테스트 가이드

백엔드워밍업클럽4기워밍업클럽백엔드

채널톡 아이콘