블로그
전체 6#카테고리
- 백엔드
2024. 10. 26.
0
[워밍업 클럽] BE 클린코드&테스트 4주차 발자국
강의 요약Persistence LayerData Access의 역할비지니스 가공 로직이 포함되어서는 안된다.Data에 대한 CRUD에만 집중한 레이어 DataJpaTestJpa 관련된 Bean만 로딩한다.상대적으로 빠름기본적으로 @Transactional이 달려있다. → 자동으로 롤백SpringBootTest모든 Bean을 로딩한다.상대적으로 느림WebMvcTestController 관련 Bean만 로딩한다.상대적으로 빠름 Business Layer비지니스 로직을 구현하는 역할Persistence Layer와의 상호작용(Data를 읽고 쓰는 행위)을 통해 비지니스 로직을 전개시킨다.트랜잭션을 보장해야한다.Presentation Layer외부 세계의 요청을 가장 먼저 받는 계층파라미터에 대한 최소한의 검증을 수행한다.Test Doublehttps://martinfowler.com/articles/mocksArentStubs.htmlDummy아무것도 하지 않는 깡통 객체Fake단순한 형태로 동일한 기능은 수행하나, 프로덕션에서 쓰기에는 부족한 객체ex) FakeRepositoryStub테스트에서 요청한 것에 대해 미리 준비한 결과를 제공하는 객체그 외에는 응답하지 않는다.상태 검증SpyStub이면서 호출된 내용을 기록하여 보여줄 수 있는 객체일부는 실제 객체처럼 동작시키고 일부만 Stubbing할 수 있다.Mock행위에 대한 기대를 명세하고, 그에 따라 동작하도록 만들어진 객체행위 검증 미션 회고3개의 테스트코드에서 중복된 내용을 @BeforeEach 로 합치는 미션.단순히 중복을 없애는 것이 아니라, 각각의 테스트가 의미를 가지도록 묶어야 하는 미션이었습니다.이 미션을 통해 Fixture를 생성할 때 Fixture의 생성에 초점이 맞춰지는 것이 아닌 테스트에 초점을 맞춰 문서의 기능을 할 수 있도록 해야한다는 것을 알았습니다.@BeforeEach void setUp() { 1-1. 2-1. 3-1. 사용자 생성에 필요한 내용 준비 1-2. 2-2. 3-2. 사용자 생성 1-3. 2-3. 3-5. 게시물 생성에 필요한 내용 준비 1-4. 2-4. 3-6. 게시물 생성 } @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 검증 } 느낀점실무에서 테스트코드를 짤 때 했던 고민들이 많이 해소되어서 좋았습니다.특히 어떤 테스트코드가 좋은지, 어떻게 작성하여야 문서로서의 기능을 할 수 있을지 알 수 있는 시간이었습니다.
백엔드
2024. 10. 25.
0
워밍업 클럽 2기 BE 클린코드&테스트코드 DAY 18 미션
1. @Mock, @MockBean, @Spy, @SpyBean, @InjectMocks 의 차이를 한번 정리해 봅시다. @Mock가짜 객체를 생성하여 단위 테스트에 사용메서드의 예상 결과를 지정할 수 있음 (stubbing 가능)@MockBeanSpring Context에 Mock 객체를 주입하여 통합 테스트에서 사용Application Context에 포함되어 다른 Bean에 의해 주입될 수 있음@Spy실제 객체를 감시하고, 호출된 메서드만 Mocking 가능일부 메서드는 실제 동작을 하고, 나머지는 Mocking 가능@SpyBeanSpring Context에 실제 객체를 주입하고 필요한 메서드만 Mocking 가능기존 Bean의 기능을 유지하면서 특정 메서드를 Mocking 가능@InjectMocksMock 객체들을 주입받을 실제 객체를 생성주입된 Mock 객체를 사용하는 클래스의 실제 동작을 테스트 가능 2. 아래 3개의 테스트가 있습니다. 내용을 살펴보고, 각 항목을 @BeforeEach, given절, when절에 배치한다면 어떻게 배치하고 싶으신가요? (@BeforeEach에 올라간 내용은 공통 항목으로 합칠 수 있습니다. ex. 1-1과 2-1을 하나로 합쳐서 @BeforeEach에 배치)@BeforeEach void setUp() { 1-1. 2-1. 3-1. 사용자 생성에 필요한 내용 준비 1-2. 2-2. 3-2. 사용자 생성 1-3. 2-3. 3-5. 게시물 생성에 필요한 내용 준비 1-4. 2-4. 3-6. 게시물 생성 } @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 검증 }
백엔드
2024. 10. 20.
0
[워밍업 클럽] BE 클린코드&테스트 3주차 발자국
강의 요약테스트란?테스트를 하는 이유, 필요한 이유빠른 피드백자동화안정감테스트 코드가 없다면?변화가 생기는 매순간마다 발생할 수 있는 모든 Case를 고려해야 한다.변화가 생기는 매순간마다 모든 팀원이 동일한 고민을 해야한다.빠르게 변화하는 소프트웨어의 안정성을 보장할 수 없다.테스트 코드가 병목이 된다면프로덕션 코드의 안정성을 제공하기 힘들다.테스트 코드 자체가 유지보수하기 어려운, 새로운 짐이 된다.잘못된 검증이 이루어질 가능성이 생긴다.올바른 테스트 코드란?자동화 테스트빠른 시간 안에 버그를 발견비용을 절약소프트웨어의 빠른 변화를 지원팀 차원의 이익단기적으론 느리지만, 장기적으로는 빠르다.단위 테스트작은 코드 단위를 독립적으로 검증하는 테스트빠름안정적JUnit 5AssertJ / Fluent assertions for javaAssertJ테스트 코드 작성을 원활하게 돕는 테스트 라이브러리풍부한 API, 메서드 체이닝 지원테스트 케이스 세분화하기테스트 코드 작성 전에 암묵적이거나 아직 드러나지 않은 요구사항이 있는가? 생각해보자금액이 0 이하인가?수량이 0 이하인가?해피 케이스와 예외 케이스경계값 테스트가 중요하다.테스트하기 어려운 영역을 분리하기테스트 할 때마다 변경되는 값이 있다면, 테스트를 정상적으로 할 수 없다.테스트 하고자 하는 영역을 잘 구분하자외부로 분리할수록 테스트 가능한 코드는 많아진다.테스트하기 어려운 영역이란?관측할 때마다 다른 값에 의존하는 코드현재 날짜/시간, 랜덤 값, 전역 변수/함수, 사용자 입력 등외부 세계에 영향을 주는 코드표준 출력, 메시지 발송, 데이터베이스에 기록하기 등// BAD public Order createOrder() { LocalDateTime currentDateTime = LocalDateTime.now(); LocalTime currentTime = currentDateTime.toLocalTime(); if (currentTime.isBefore(SHOP_OPEN_TIME) || currentTime.isAfter(SHOP_CLOSE_TIME)) { throw new IllegalArgumentException("주문 시간이 아닙니다."); } return new Order(currentDateTime, beverages); } // GOOD public Order createOrder(LocalDateTime currentDateTime) { LocalTime currentTime = currentDateTime.toLocalTime(); if (currentTime.isBefore(SHOP_OPEN_TIME) || currentTime.isAfter(SHOP_CLOSE_TIME)) { throw new IllegalArgumentException("주문 시간이 아닙니다."); } return new Order(currentDateTime, beverages); } TDD테스트 주도 개발 Test Driven Development프로덕션 코드보다 테스트 코드를 먼저 작성하여 테스트가 구현 과정을 주도하도록 하는 개발 방법론RED → GREEN → REFACTOR실패하는 테스트 작성테스트가 통과하도록 최소한의 코딩구현 코드 개선, 테스트 통과 유지하면서 리팩토링선 기능 구현, 후 테스트 작성 시테스트 자체의 누락 가능성특정 테스트 케이스만 검증할 가능성해피 케이스잘못된 구현을 다소 늦게 발견할 가능성선 테스트 작성, 후 기능 구현 시복잡도가 낮은, 테스트 가능한 코드로 구현할 수 있게 한다.유연하면서 유지보수가 쉬움쉽게 발견하기 어려운 엣지 케이스를 놓치지 않게 해준다.구현에 대한 빠른 피드백을 받을 수 있다.과감한 리팩토링이 가능해진다.테스트 코드는 문서다프로덕션 기능을 설명하는 테스트 코드 문서다양한 테스트 케이스를 통해 프로덕션 코드를 이해하는 시각과 관점을 보완어느 한 사람이 과거에 경험했던 고민의 결과물을 팀 차원으로 승격시킴→ 모두의 자산으로 공유할 수 있다.문서의 기능을 하기 위해서…@DisplayName 을 섬세하게테스트 행위에 대한 결과까지 기술하기BAD : 음료 1개 추가 테스트GOOD : 음료를 1개 추가할 수 있다.BEST : 음료를 1개 추가하면 주문 목록에 담긴다!도메인 용어를 사용하여 한층 추상화된 내용을 담기BAD : 특정 시간 이전에 주문을 생성하면 실패한다.GOOD : 영업 시작 시간 이전에는 주문을 생성할 수 없다.테스트의 현상을 중점으로 기술하지 말 것BDDBehavior Driven DevelopmentTDD에서 파생된 개발 방법함수 단위의 테스트에 집중하기보다, 시나리오에 기반한 테스트케이스(TC) 자체에 집중개발자가 아닌 사람이 봐도 이해할 수 있을 정도의 추상화 수준을 권장 Given / When / ThenGiven : 시나리오 진행에 필요한 모든 준비 과정 (객체, 값, 상태 등)When : 시나리오 행동 진행Then : 시나리오 진행에 대한 결과 명시, 검증어떤 환경에서 (Given)어떤 행동을 진행했을 때 (When)어떤 상태 변화가 일어난다 (Then)→ DisplayName에 명확하게 작성할 수 있다. 미션 회고읽기 쉬운 코드에서 작성하였던 지뢰찾기를 기반으로 테스트 코드를 작성하였다.어떤 코드를 테스트 해야할 지, 어떻게 테스트 코드를 짜야할 지 깊이 고민하게 되었다. 느낀점테스트코드에 대해 어느 정도 감이 생기고 있는 것 같다. 하지만 여전히 실무에 적용하기엔 너무 어렵다.비지니스 로직이 변경되더라도 테스트 코드가 어느정도 내구성을 가졌으면 하는데, 이렇게 작성하는게 매우 어렵다.또한 Given 절에서 객체를 사전준비하는 과정이 너무 번거롭다.이를 해결할 수 있는 좋은 방법이 있을 것 같은데..여전히 테스트 코드는 귀찮고 어렵다.
백엔드
2024. 10. 13.
0
[워밍업 클럽] BE 클린코드&테스트 2주차 발자국
강의 요약능동적 읽기복잡하거나 엉망인 코드를 읽고 이해하려 할 때, 리팩토링을 하자.공백으로 단락 구분메서드와 객체와 추상화 해보기주석으로 이해한 내용 표기하며 읽기언제든지 rollback 할 수 있다겁먹지 말자목표는 도메인 지식을 늘리는 것.작성자의 의도를 파악하는 것.오버 엔지니어링적정 수준보다 더 높은 수준의 엔지니어링구현체가 하나인 인터페이스아키텍처 이해에 도움을 주거나, 추가될 가능성이 높다면 OK구현체를 수정할 때마다 인터페이스를 수정해야함코드 탐색에 영향을 줌. 애플리케이션이 비대해 짐.너무 이른 추상화정보가 숨겨지기 때문에 복잡도가 높아진다.의도 파악하기 어렵다.경험의 영역이다. 경험을 쌓자.은탄환은 없다. (No Silver Bullet)체스를 코딩하려면?인터페이스와 클래스로 구현하자! → 유지보수 하기 쉬울거야!but 체스는 500년동안 변하지 않았다..클린코드는 은탄환이 아니다.지속가능한 소프트웨어의 품질 vs 기술 부채를 안고 가는 빠른 결과물둘 사이에서의 줄다리기를 잘 해야한다.클린 코드를 항상 생각하면서 추후 수정이 쉽도록 작성해야 한다.모든 기술과 방법론은 적정 기술의 범위 내에서 사용되어야 한다.도구라는 것은, 일단 그것을 한계까지 사용할 줄 아는 사람이 그것을 사용하지 말아야 할 때도 아는 법이다.적정 수준을 알기 위해, 때로는 극단적으로 시도해보자.경험이 중요하다. 미션 회고이번 미션은 StudyCafePassMachine 의 코드를 리팩토링 하는 것이었다.처음 코드를 받아본 순간 막막했다. 어디서부터 어떻게 리팩토링 해야하지? 이렇게 하는 게 맞을까? 라는 고민이 끝없이 이어져 끝내 시간내에 미션을 완료하지 못하였다.무한한 고민속에 잠식되어 있다가, 결국 강의를 보면서 리팩토링을 따라갔다.가장 인상깊었던 리팩토링은 추상화 관점의 차이이다. 초기 FileHandler로 짜여져 있던 코드를 Provider 로 추상화 한 후, 이를 구체화한 FileProvider로 리팩토링하였다.이를 통해 파일로 입력을 받지 않고, 구글 sheet나 엑셀로 입력을 받더라도 구현체만 새로 구현하면 기존 코드를 고칠 필요가 없이 편하게 유지보수가 가능해지기 때문이다.고수준에 의존하지 않고 추상화한 저수준에 의존하여 코드를 작성한다. 라는 말이 이 강의를 통해 와닿게 되었다.이런 상황이 현재 재직중인 회사에서 정말 많이 나타나는데, 회사에 적용하여 유지보수하기 쉬운 코드를 작성할 수 있을 것 같다. 느낀점가장 좋았던 점은 금요일에 중간점검을 통해 다른 분들의 코드를 같이 보는 것이었다.강사님이 코드리뷰를 진행하면서 나와 다른 사람들의 생각의 공통점과 차이점을 알 수 있었다.또한, 강사님의 친절한 피드백이 코드리뷰에만 적용되는 것이 아니라 지금까지 내가 작성해온 코드, 추후 내가 작성할 코드에 대해 이정표를 제시해 주는 것 같아 매우 만족스러웠다.
2024. 10. 06.
0
워밍업 클럽 2기 BE 클린코드&테스트코드 1주차 발자국
강의 내용 중 중요하다고 생각되는 부분추상이름 짓기의 중요성 단수와 복수 구분하기이름 줄이지 않기은어/방언 사용하지 않기좋은 코드를 보고 습득하기당연하다고 생각되지만 생각보다 지키기 쉽지 않다....추상화 수준추상화 수준을 맞춰야 독자로 하여금 읽기 쉬운 코드가 된다.납득은 되지만, 실제 코드에 적용하게 되면 private method가 너무 많아지는 것은 아닌가... 뭐든 적당히...논리 사고의 흐름사고의 depth 줄이기중첩 분기문, 중첩 반목문 줄이기사용할 변수는 가깝게 선언하기무조건 적으로 depth를 줄이기 보다는, 사고의 depth를 줄이도록, 독자로 하여금 읽기 쉽게 짜야함...해피케이스와 예외처리예외가 발생할 가능성 낮추기NPE 를 방지하는 방향으로 작성, Optional 사용도 고민해봐야 함의도한 예외와 예상하지 못한 예외를 구분하기객체 지향 패러다임단일 책임 원칙 (Single Responsibility Principle): 클래스는 하나의 책임만 가져야 하며, 하나의 변화 이유만 가져야 한다.개방-폐쇄 원칙 (Open/Closed Principle): 소프트웨어 요소는 확장에는 열려 있어야 하지만, 수정에는 닫혀 있어야 한다.리스코프 치환 원칙 (Liskov Substitution Principle): 서브클래스는 언제나 자신의 기반 클래스와 호환되어야 한다.인터페이스 분리 원칙 (Interface Segregation Principle): 클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다.의존성 역전 원칙 (Dependency Inversion Principle): 고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 둘 다 추상화에 의존해야 한다.항상 인식하고 있지만, 실제 적용하기는 매우 어려운... 객체 ... 미션1. Day2 일상생활에서 쉽게 찾을 수 있는 추상과 구체에 대한 생각."출근" 이라는 추상을 구체화 하여, 아침에 일어나고 회사로 이동하고 업무 준비를 하는 과정으로 구체화2. Day 4미션의 요구사항주문 항목이 0 이상이어야 한다.총 주문 금액이 0 이상이어야 한다.주문을 하는 사용자의 정보가 있어야 한다.미션 해결 과정가독성을 위해 depth를 줄임.ealry return 을 이용하여 불필요한 else 를 줄임.추상화 수준에 맞게 메소드로 추출// before public 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; }// after public boolean validateOrder(Order order) { if (hasNoItems(order)) { log.info("주문 항목이 없습니다."); return false; } if (hasInvalidTotalPrice(order)) { log.info("올바르지 않은 총 가격입니다."); return false; } if (hasNoCustomerInfo(order)) { log.info("사용자 정보가 없습니다."); return false; } return true; }
백엔드
2024. 10. 03.
0
워밍업 클럽 2기 BE 클린코드&테스트코드 DAY 4 미션
public boolean validateOrder(Order order) { if (hasNoItems(order)) { log.info("주문 항목이 없습니다."); return false; } if (hasInvalidTotalPrice(order)) { log.info("올바르지 않은 총 가격입니다."); return false; } if (hasNoCustomerInfo(order)) { log.info("사용자 정보가 없습니다."); return false; } return true; }SOLID단일 책임 원칙 (Single Responsibility Principle):클래스는 오직 하나의 책임만 가져야 하며, 변경의 이유도 하나여야 합니다.이를 통해 클래스는 더 작고 관리하기 쉬운 단위로 나누어질 수 있습니다.여러 책임을 가진 클래스는 수정 시 영향 범위가 커지고, 유지보수성이 낮아질 수 있습니다.개방-폐쇄 원칙 (Open/Closed Principle):소프트웨어는 기능을 확장할 수 있어야 하지만, 기존 코드를 수정하지 않아야 합니다.이는 주로 인터페이스나 추상화를 통해 달성되며, 코드 수정 없이 새로운 기능을 추가할 수 있게 합니다.이 원칙을 지키면 기존 기능이 안정적으로 유지되고, 새로운 요구사항에 대응하기 쉬워집니다.리스코프 치환 원칙 (Liskov Substitution Principle):자식 클래스는 부모 클래스에서 기대되는 기능을 모두 수행할 수 있어야 합니다.자식 클래스가 부모 클래스 대신 사용되더라도 프로그램의 기능은 변하지 않아야 합니다.이를 지키면 클래스 간 상속 구조에서 예기치 않은 오류를 방지할 수 있습니다.인터페이스 분리 원칙 (Interface Segregation Principle):클라이언트는 자신이 사용하지 않는 인터페이스나 메서드에 의존하지 않아야 합니다.큰 인터페이스를 작게 분리함으로써 클라이언트가 필요하지 않은 기능에 의존하는 것을 피할 수 있습니다.이렇게 하면 불필요한 코드 의존성을 줄이고 유지보수성을 높일 수 있습니다.의존성 역전 원칙 (Dependency Inversion Principle):고수준 모듈은 저수준 모듈에 의존해서는 안 되며, 둘 다 추상화에 의존해야 합니다.이는 구체적인 구현보다는 인터페이스나 추상 클래스를 통해 모듈 간 의존성을 줄이는 방식입니다.이 원칙을 따르면 시스템이 유연하고 확장 가능해지며, 변경에 더 잘 대응할 수 있습니다.
백엔드