블로그
전체 72024. 10. 26.
0
네번째 발자국 👣
Presentation Layer 테스트외부세계의 요청을 가장 먼저 받는 계층파라미터에 대한 최소한의 검증을 수행한다.@Mock, @Spy, @InjectMocks@Mock : Mock(가짜) 객체로 만든다@InjectMocks : Mock 객체를 주입해준다@Spy : 기능들중 일부만 Mock으로 만든다.더 나은 테스트를 작성하기 위한 구체적 조언한문단에 한 주제완벽하게 제어하기 : 현재시간 랜덤 값 등 외부시스템과 연동하는 경우 상위계층으로테스트 환경의 독립성을 보장하자 : given 환경에서 다른 api 사용 지양, 생성자로만 구성하는 것이 좋다테스트간 독립성을 보장하자 : 공유자원 사용하지 않아야 한다.한눈에 들어오는 Test Fixture 구성하기Test Fixture 클렌징 : deleteAll() 와 deleteAllInBatch() 구분해서 사용@ParameterizedTest : 값이나 데이터를 바꿔가면서 테스트 하고싶을 때@DynamicTest : 시나리오 기반으로, 환경을 공유하면서 테스트 할수있다.테스트 수행도 비용이다. 환경통합하기학습테스트: 테스트를 하면서 학습하기Spring REST Docs: 테스트 코드를 통한 API 문서 자동화 도구 미션 ✔ 게시판 게시물에 달리는 댓글을 담당하는 Service Test ✔ 댓글을 달기 위해서는 게시물과 사용자가 필요하다. ✔ 게시물을 올리기 위해서는 사용자가 필요하다. 👇 처음 내가 작성했던 코드 @BeforeEach void setUp() { 1-1. 사용자 생성에 필요한 내용 준비 1-3. 게시물 생성에 필요한 내용 준비 1-5. 댓글 생성에 필요한 내용 준비 } @DisplayName("사용자가 댓글을 작성할 수 있다.") @Test void writeComment() { // given 1-2. 사용자 생성 1-4. 게시물 생성 // when 1-6. 댓글 생성 // then 검증 } @DisplayName("사용자가 댓글을 수정할 수 있다.") @Test void updateComment() { // given 2-2. 사용자 생성 2-4. 게시물 생성 2-6. 댓글 생성 // when 2-7. 댓글 수정 // then 검증 } @DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.") @Test void cannotUpdateCommentWhenUserIsNotWriter() { //given 3-2. 사용자1 생성 3-6. 사용자2 생성 3-6. 사용자1 게시물 생성 3-8. 사용자1의 댓글 생성 //when 3-9. 사용자2가 사용자1의 댓글 수정 시도 // 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 3-9 사용자2가 사용자1의 댓글 수정 시도 // then 검증 } 미션 회고 처음엔 setUp()에 생성에 필요한 내용들만 배치해야한다고 생각하고 저렇게 코드를 작성했는데,댓글 도메인에 대한 테스트라는 것에 중점을 둬야한다는 공통 피드백을 듣고 다시 수정했다. 앞으로 항상 도메인을 중심으로 생각하는 습관을 들여야겠다. 넷째주 회고 벌써 4주가 지났다니 시간이 너무 빠르다. 그동안 많은 것들을 배웠는데, 처음 리팩토링 하는것부터 테스트하는것까지 다시한번 쭉 훑어봐야겠다. 개인적으로 새로 알게된 것이 많은데, 그 중에서 코드를 보는 관점을 많이 배운것 같다. 배웠던 것들을 내것으로 완전히 만들기까지 많은 시간이 걸리겠지만, 꼭 복습을 해서 스스로 적용할 수 있도록 해야겠다. 두 강의를 들으면서 스스로 더 공부를 해보고싶다는 자극을 많이 받았다. 의미있는 시간이었다.
2024. 10. 25.
0
미션 DAY 18
1. @Mock, @MockBean, @Spy, @SpyBean, @InjectMocks 의 차이를 한번 정리해 봅시다.@Mock : 가짜 객체를 만들어준다.@MockBean : spring context가 필요한 통합테스트에서 사용한다.@Spy : 여러기능 중 쓰고싶은 곳만 stubbing하고 다른 기능은 실제 객체가 수행한다.@SypBean : spring context가 필요한 통합테스트에서 사용한다.@InjectMocks : 선언된 가짜 객체를 주입해 주는 기능 2. 아래 3개의 테스트가 있습니다. 내용을 살펴보고, 각 항목을 @BeforeEach, given절, when절에 배치한다면 어떻게 배치하고 싶으신가요? (@BeforeEach에 올라간 내용은 공통 항목으로 합칠 수 있습니다. ex. 1-1과 2-1을 하나로 합쳐서 @BeforeEach에 배치) @BeforeEach void setUp() { 1-1. 사용자 생성에 필요한 내용 준비 1-3. 게시물 생성에 필요한 내용 준비 1-5. 댓글 생성에 필요한 내용 준비 } @DisplayName("사용자가 댓글을 작성할 수 있다.") @Test void writeComment() { // given 1-2. 사용자 생성 1-4. 게시물 생성 // when 1-6. 댓글 생성 // then 검증 } @DisplayName("사용자가 댓글을 수정할 수 있다.") @Test void updateComment() { // given 2-2. 사용자 생성 2-4. 게시물 생성 2-6. 댓글 생성 // when 2-7. 댓글 수정 // then 검증 } @DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.") @Test void cannotUpdateCommentWhenUserIsNotWriter() { //given 3-2. 사용자1 생성 3-6. 사용자2 생성 3-6. 사용자1 게시물 생성 3-8. 사용자1의 댓글 생성 //when 3-9. 사용자2가 사용자1의 댓글 수정 시도 // then 검증 }
2024. 10. 22.
0
미션 Day 15
Presentation 계층사용자의 입력을 받아 Business Layer로 전달 파라미터에 대한 최소한의 검증을 수행한다Business Layer와 Persistence Layer를 Mocking 처리 후 테스트 한다.@WebMvcTest 사용request 파라미터 값이 유효한지 검증한 후, 응답 상태코드나 에러메세지 출력을 확인한다.Business 계층비즈니스 로직을 구현하는 역할 트랜잭션을 보장해야한다. 읽기전용 트랜잭션과 Command 트랜잭션을 적절히 사용해야한다.@SpringBootTest 사용 실제 데이터를 저장한 후 데이터 로직을 검증한다. tearDown메서드로 테스트 각각의 독립성을 유지한다. Persistence 계층 Data Access 역할 데이터에 대한 CRUD에만 집중한 레이어 비지니스 로직이 포함되면 안된다. @SpringBootTest나 @DataJpaTest 어노테이션을 사용한다.리포지토리에 작성한 쿼리를 각각 검증한다.
2024. 10. 20.
0
세번째 발자국 👣
테스트는 왜 필요할까?-테스트를 통해 얻고자 하는것빠른 피드백자동화안정감단위테스트작은 코드단위를 독립적으로 검증하는 테스트검증속도가 빠르고, 안정적이다테스트 케이스 세분화하기해피케이스예외케이스경계값 테스트 ( 범위(이상,이하,초과,미만)구간 날짜 등)테스트하기 어려운 영역을 분리하기테스트하기 어려운 영역-> 관측할 때마다 다른 값에 의존하는 코드현재날짜/시간, 랜덤 값, 전역 변수/함수, 사용자 입력 등 -> 외부세계에 영향을 주는 코드표준 출력, 메시지 발송 , 데이터베이스에 기록하기 등TDD : Test Driven Development프로덕션 코드보다 테스트 코드를 먼저 작성하여 테스트가 구현과정을 주도하도록 하는 방법론레드: 실패하는 테스트 작성 -> 그린: 최소한의 코딩 -> 리팩터: 구현코드 개선 테스트 통과유지클라이언트 관점에서의 피드백을 주는 Test Driven테스트는 []다프로덕선 기능을 설명하는 테스트 코드 문서DisplayName을 섬세하게-명사의 나열보다 문장으로-테스트 행위에 대한 결과까지 기술하기-도메인 용어를 사용하여 한층 추상화된 내용을 담기-테스트 현상을 중점으로 기술하지 말것BDD 스타일로 작성하기-TDD에서 파생된 개발 방법-함수 단위의 테스트 보다 시나리오에 기반한 테스트케이스자체에 집중-개발자가 아닌 사람이 봐도 이해할수 있을 정도의 추상화 수준을 권장Given / When / ThenGiven : 시나리오 진행에 필요한 모든 준비 과정When : 시나리오 행동 진행Then : 시나리오 진행에 대한 결과 명시, 검증 Layerd Architecture와 테스트Layerd Architecture -> 관심사의 분리Persistence LayerData에 대한 CRUD에만 집중한 레이어 Data Access의 역할비지니스 가공 로직이 포함 되어서는 안된다.Business Layer비즈니스 로직을 구현하는 역할Persistence Layer와의 상호작용(Data를 읽고 쓰는 행위)를 통해 비즈니스 로직을 전개시킨다.트랜잭션을 보장해야한다.테스트 코드 작성하기 미션 회고 이렇게 하는 것이 맞는지 계속 의문이 들었다. DisplayName을 짓는 것도 어렵고 쉽지 않았다. 예외 케이스를 생각하는게 쉽지않았다.
2024. 10. 13.
0
두번째 발자국 👣
코드 다듬기주석의 양면성변수와 메서드의 나열순서 : 나열순서로 의도와 정보를 전달할수 있다. 패키지 나누기IDE의 도움 받기리팩토링 미션 회고리팩토링을 직접해보려니 생각보다 너무 어려웠다. 🥲 배웠던 접근 방법도 중구난방으로 생각이 나고, 내 생각을 코드로 나타내는 것이 쉽지않았다. 그래도 먼저 내가 생각했던 방식을 기록하고, 다시 강의를 보면서 깨달았던 부분을 적어봐야겠다.먼저 코드를 읽으면서 코드가 어떤의미를 가지고 있는지 파악했다. 그리고 패스 타입에따라 나누어진 부분에서 똑같이 반복되는 부분을 밖으로 빼서 메서드화 하면 될거라고 생각했다. 그런데 마지막 조건문에서 락커에 대한 조건 문이 추가 되었는데 그 부분을 어떻게 처리해야할지 고민이 됐다.그리고 스터디 패스권의 모음이 리스트 형식이라서, 이걸 한번 감싼 형태인 일급컬렉션을 사용해야한다는 생각도 들었다.그리고 InputHandler, OutputHandler, FileHandler는 인터페이스로 써야하는것아닌가 라는 생각이 들었다.강의를 들어보니, 무조건 메서드를 만들려고 했던 것이 더 혼란을 일으켰던 것 같고, 생각한 것을 코드로 옮기는 구현에 대한 연습이 더 필요한것과 객체의 책임에 대해 더욱더 생각해야한다는 깨달음을 얻었다. 어찌됐던 지금의 나에게는 좀 더 많은 복습이 필요하다. 너무 해야할게 많은거 아닌가 싶어서 압박감이 들기도 했지만 , 하나씩 알아가는 재미도 분명 있기때문에 힘들지는 않다. 내가 생각했던 과정을 수정해가는 과정을 통해서 조금 더 나은 내가 될 것이다.중간 점검 회고 나에게 필요한 것 메서드 분리하는 것부터 시작해보기책임을 정의하고 객체로도 나눠보기한글로 풀어써보는 것도 좋다. 프로젝트 추천 책 - 스프링 부트와 AWS로 혼자 구현하는 웹 서비스추천 책 - 함께 자라기
2024. 10. 06.
0
첫째주 발자국 👣
추상화이름짓기 메서드 선언부의 의미 추상화 레벨 추상화 레벨이 동등해야한다.매직넘버, 매직 스트링 상수로 추출하기논리, 사고의 흐름 뇌 메모리 적게쓰기 Early return, 사고의 depth 줄이기 공백 라인이 가지는 의미, 부정어 - 긍정문으로 바꾸기 해피 케이스와 예외처리 - 예외에 더 집중하자. Null 처리 주의하기 객체 지향 패러다임 추상의 관점으로 바라보는 객체 지향 객체 = 데이터+ 코드 객체설계 : 관심사의 분리 -> 높은 응집도와 낮은 결합도getter/ setter 자제하기 객체에 메시지 보내기 SOLID: SRP, OCP, LSP,ISP,DIPSRP : 단일 책임의 원칙OCP : 확장에는 열려있고 수정에는 닫혀있다.LSP : 부모가 있는곳을 자식이 대체할수있어야한다.ISP : 인터페이스를 기능단위로 쪼갠다.DIP : 고수준 모듈과 저수준모듈 각각 추상화를 의존하도록 하기 객체 지향 적용하기상속과 조합조합과 인터페이스를 활용하는것이 유연한 구조Value Object 도메인의 어떤 개념을 추상화하여 표현한 값 객체 일급컬렉션 : 컬렉션을 유일한 필드로 가지는 객체👩🏼💻첫째주 회고미션1 : 세수한다를 구체적으로 적어보았는데, 하나하나 생각해보니 저 단어에 참많은 행동들이 축약되있구나 라는 생각이 들었다. 추상화를 좀 더 이해할 수 있는 미션이었다.미션2 : 코드를 리팩토링 하는 것이었는데, if-else문이 너무 많아서 복잡해보였다. 그래서 얼리 리턴 구조로 우선 변경을 하고, if문 조건문에 ! (부정어)가 쓰여서 메서드를 추출해서 긍정문으로 바꿨다. 미션을 제출하고 보니 메서드중에 get으로 꺼내는게 있던데 이것도 수정해야하는 거 아닌가 라는 생각이 들었다. 강의 듣는 내내, 새롭게 접하는 부분이 많아서 이해하고 생각할 거리가 많았다. 그래서 하루에 해야할 분량이 너무 많게 느껴져 힘들었다. 그래도 끝까지 하는게 목표! 다음주도 화이팅 👏출처 https://inf.run/kHiWM
2024. 10. 03.
0
미션 Day4
미션 Day4 리팩토링하기 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; }public boolean validateOrder(Order order) { if (isEmptyOrder(order)) { log.info("주문 항목이 없습니다."); return false; } if (hasNoCustomerInfo(order)) { log.info("사용자 정보가 없습니다."); return false; } if (isInValidTotalPrice(order)) { log.info("올바르지 않은 총 가격입니다."); return false; } return true; } private boolean isInValidTotalPrice(Order order) { return !(order.getTotalPrice() > 0); } private boolean hasNoCustomerInfo(Order order) { return !order.hasCustomerInfo(); } private boolean isEmptyOrder(Order order) { return order.getItems().size() == 0; } SOLID에 대해 나의 언어로 정의하기 SRP : 하나의 클래스는 하나의 책임만을 가져야 한다. OCP : 기능 추가에는 열려있고, 수정에는 닫혀있어야 한다. LSP : 상속관계에서 자식클래스는 부모클래스의 기능을 대신할수있다. ISP : 인터페이스는 필요한 기능만 가지게 만들어야한다. DIP : 추상클래스에 의존하도록 만들어 교체가 쉽게 이루어질수있게 한다.