블로그
전체 62025. 03. 29.
0
워밍업 클럽 3기 BE 클린코드&테스트 - 4주차 발자국
회고변경이 있을때 테스트는 깨져야 한다. 변경의 영향도를 확인할 수 있기 때문처음 테스트를 작성할때는 클래시스트였는데 테스트가 깨지는게 나쁘지 않다면 속도를 고려해서 mock을 활용하는 게 더 좋지 않을까?어차피 각 계층별 테스트를 하는데 하위 계층을 모킹을해서 속도를 높이는게 더 좋을 거 같다.미션@Mock, @MockBean, @Spy, @SpyBean, @InjectMocks 의 차이MockSpring Context 없이 순수하게 Mock 객체를 만들어서 사용InjectMocksMock은 Spring Context에서 관리해주지 않기 때문에 해당 Mock객체가 필요한 객체에 선언하여 Mock객체를 주입MockBeanSpring Context 에서 관리하는 빈 대신 사용하기 위해 사용MockBean을 사용하면 Srping Context에 있는 빈을 대신해서 사용하기 때문에 Spring에서 의존성 주입을 해준다. Spymock은 가짜객체라서 지정한 행동말고는 아무것도 하지 않지만진짜객체를 기반으로 생성되어 일부 행동에 대해 지정한 값을 반환Mock이 when과 thenReturn을 지정하지만Spy는 진짜 객체를 기반으로 행동하기에 doSometing(return)만 지정실제객체를 기반으로 만들기에 stub과 실제 메소드 혼합해서 테스트 가능SpyBeanmock과 mockbean같은 관계2. 아래 3개의 테스트가 있습니다.내용을 살펴보고, 각 항목을 @BeforeEach, given절, when절에 배치한다면 어떻게 배치하고 싶으신가요?(@BeforeEach에 올라간 내용은 공통 항목으로 합칠 수 있습니다. ex. 1-1과 2-1을 하나로 합쳐서 @BeforeEach에 배치)@BeforeEach void setUp() { 사용자1 생성에 필요한 내용 준비 사용자1 생성 사용자2 생성에 필요한 내용 준비 사용자2 생성 사용자1의 게시물 생성에 필요한 내용 준비 사용자1의 게시물 생성 } @DisplayName("사용자가 댓글을 작성할 수 있다.") @Test void writeComment() { // given 댓글 생성에 필요한 내용 준비 // when 댓글 생성 // then 검증 } @DisplayName("사용자가 댓글을 수정할 수 있다.") @Test void updateComment() { // given 댓글 생성에 필요한 내용 준비 댓글 생성 // when 댓글 수정 // then 검증 } @DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.") @Test void cannotUpdateCommentWhenUserIsNotWriter() { // given 사용자1의 댓글 생성에 필요한 내용 준비 사용자1의 댓글 생성 // when // then 사용자2가 사용자1의 댓글 수정 시도 검증 }
2025. 03. 25.
0
워밍업 클럽 3기 BE 클린코드&테스트 DAY-18 미션
1. @Mock, @MockBean, @Spy, @SpyBean, @InjectMocks 의 차이MockSpring Context 없이 순수하게 Mock 객체를 만들어서 사용InjectMocksMock은 Spring Context에서 관리해주지 않기 때문에 해당 Mock객체가 필요한 객체에 선언하여 Mock객체를 주입MockBeanSpring Context 에서 관리하는 빈 대신 사용하기 위해 사용MockBean을 사용하면 Srping Context에 있는 빈을 대신해서 사용하기 때문에 Spring에서 의존성 주입을 해준다. Spymock은 가짜객체라서 지정한 행동말고는 아무것도 하지 않지만진짜객체를 기반으로 생성되어 일부 행동에 대해 지정한 값을 반환Mock이 when과 thenReturn을 지정하지만Spy는 진짜 객체를 기반으로 행동하기에 doSometing(return)만 지정실제객체를 기반으로 만들기에 stub과 실제 메소드 혼합해서 테스트 가능SpyBeanmock과 mockbean같은 관계2. 아래 3개의 테스트가 있습니다. 내용을 살펴보고, 각 항목을 @BeforeEach, given절, when절에 배치한다면 어떻게 배치하고 싶으신가요?(@BeforeEach에 올라간 내용은 공통 항목으로 합칠 수 있습니다. ex. 1-1과 2-1을 하나로 합쳐서 @BeforeEach에 배치)@BeforeEach void setUp() { 사용자1 생성에 필요한 내용 준비 사용자1 생성 사용자2 생성에 필요한 내용 준비 사용자2 생성 사용자1의 게시물 생성에 필요한 내용 준비 사용자1의 게시물 생성 } @DisplayName("사용자가 댓글을 작성할 수 있다.") @Test void writeComment() { // given 댓글 생성에 필요한 내용 준비 // when 댓글 생성 // then 검증 } @DisplayName("사용자가 댓글을 수정할 수 있다.") @Test void updateComment() { // given 댓글 생성에 필요한 내용 준비 댓글 생성 // when 댓글 수정 // then 검증 } @DisplayName("자신이 작성한 댓글이 아니면 수정할 수 없다.") @Test void cannotUpdateCommentWhenUserIsNotWriter() { // given 사용자1의 댓글 생성에 필요한 내용 준비 사용자1의 댓글 생성 // when // then 사용자2가 사용자1의 댓글 수정 시도 검증 }
2025. 03. 24.
0
워밍업 클럽 3기 BE 클린코드&테스트 DAY-16 미션
레이어별로 1) 어떤 특징이 있고, 2) 어떻게 테스트를 하면 좋을지 공통적으로 각 계층에서 해당 테스트를 진행하기에 다른 계층에 객체가 필요할 경우 목객체를 활용해서 테스트완료 속도를 높이는 것이 좋아보인다.테스트 코드에서는 조건문과 같은 논리적인 연산을 사용하지 않는다. Presentation Layer외부세계로부터 입출력을 담당하는 계층 사용자의 요청을 받아서 이를 비즈니스 로직에 전달하고, 결과를 사용자에게 응답컨트롤러 메소드의 입력 파라미터 유효성 검증, 반환값 검증등을 하는 테스트 수행Business Layer핵심 비즈니스 로직을 담당하는 계층 각 비즈니스 메서드들이 정상적인 상황과 예외적인 상황에서 비즈니스 규칙대로 작동하는 확인하는 테스트 수행Persistence Layer비즈니스로직에 필요한 데이터베이스를 담당하는 계층 테스트DB와 같은 것을 활용하여 DB에 CRUD 작업이 되는지 확인하는 테스트 수행
2025. 03. 23.
0
워밍업 클럽 3기 BE 클린코드&테스트 - 3주차 발자국
강의 수강스프링 & JPA 기반 테스트 #5 #6 #7 #8 #9Layered Architecture단위 테스트 vs 통합 테스트Ioc, DI, AOPORM, 패러다임의 불일치, HibernateString Data JPA@SpringBootTest vs @DataJpaTest@SpringBootTest vs @WebMvcTest@Transactional(readOnly = true)@RestController, @ExceptionHandleSpring bean validation notnull, not empty, not blank@WebMvcTestObjectMapperMock, Mockito, @MockBean미션[Readable Code] 강의의 두 프로젝트(지뢰찾기, 스터디카페) 중 하나를 골라, 단위 테스트를 작성해 봅시다. 조건은 아래와 같습니다.스터디 카페로 테스트 코드 작성getter 같은 기본적인 메서드 제외하고 모든 단위 테스트 작성테스트하기 힘든 코드(상수로 선언된 path가 필요한 메서드)경우 테스트 만을 위한 코드 새로 만들어서 테스트(path 는파라미터로 주입) public StudyCafeLockerPasses getLockerPassesTest(final String path) { try { List lines = Files.readAllLines(Paths.get(path)); List lockerPasses = new ArrayList(); for (String line : lines) { String[] values = line.split(","); StudyCafePassType studyCafePassType = StudyCafePassType.valueOf(values[0]); int duration = Integer.parseInt(values[1]); int price = Integer.parseInt(values[2]); StudyCafeLockerPass lockerPass = StudyCafeLockerPass.of(studyCafePassType, duration, price); lockerPasses.add(lockerPass); } return StudyCafeLockerPasses.of(lockerPasses); } catch (IOException e) { throw new RuntimeException("파일을 읽는데 실패했습니다.", e); } }https://github.com/wjdrltjr5/inflearn-warming-up/tree/lesson6-4회고상수가 필요한 코드같은 경우 path를 파라미터로 받게 하는 것이 테스트 하기가 더 편리할 것 같아서 수정하였다이렇게 하는 것이 맞는 것인지는 모르겠다.
2025. 03. 16.
0
워밍업 클럽 3기 BE 클린코드&테스트 - 2주차 발자국
강의 수강주석의 양면성주석은 죄악 vs 주석 좀 남겨줘라주석이 많다는 것은 코드로 잘 표현을 하지 못했다는 의미후대에 전해야할 의사 결정의 히스토리를 코드로 표현할 수 없을때 주석으로 상세하게 설명(이런 정책 선정의 이유)주석 작성시 자주 변하는 정보는 최대한 지양코드 업데이트시 당연히 문서도 업데이트변수와 메서드의 나열 순서변수사용하는 순서대로 나열메서드의 순서 객체의 입장에서 생각패키지 나누기패키지는, 문맥으로써의 정보를 제공할 수 있다.패키지를 쪼개지 않으면 관리가 어려워진다.패키지를 너무 잘게 쪼개도 마찬가지로 관리가 어려워진다.리팩토링 포인트추상화 레벨객체로 묶어볼만한 것은 없는지객체지향 패러다임에 맞게 객체들이 상호 협력하고 있는지SRP : 책임에 따라 응집도 있게 객체가 잘 나뉘어져 있는지DIP : 의존관계 역전을 적용할 만한 곳은 없는지일급 컬렉션은탄환은 없다.미션패스를 두종류의 인터페이스를 만들고 해당 구현체를 사용하게 하였음display 같은 경우도 pass의 정보를 출력하는 거니까 pass에 구현하였음머신의 경우 분기문을 메서드로 추출주문객체를 생성하여 주문정보를 출력하게 수정https://github.com/wjdrltjr5/inflearn-warming-up/tree/refactoring회고아 시간이 부족해서 리팩토링 과제에 충분히 시간을 쏟지 못해서 고민할만한 포인트나 결과물을 만들어 내지 못했고 코드리뷰를 신청하지 못한것이 후회가 된다.
백엔드
・
워밍업클럽
・
발자국
2025. 03. 08.
0
워밍업 클럽 3기 BE 클린코드&테스트 - 1주차 발자국
강의 수강개발이란 팀단위로 진행하는 프로젝트이기에 다른 팀원 미래의 나 미래의 유지보수를 위해 가독성있는 코드를 작성해야 한다는 것코드를 작성하는 시간보다 코드를 읽는 시간이 많다는 것미션 // 기존 코드 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(order.isEmpty()){ log.info("주문 항목이 없습니다."); return false; } if(order.hasNotCustomerInfo()){ log.info("사용자 정보가 없습니다."); return false; } if(order.isNotPriceValid()){ log.info("올바르지 않은 총 가격입니다."); return false; } return true; }회고발자국을 작성하면서 다시한번 미션 코드를 보니 validateOrder메소드의 위치가 바뀌어야 한다는 생각이 든다.단일책임원칙을 생각했을때 주문에 대한 책임은 Order가 가지고 있는데 그러니 주문을 검증하는 코드역시 Order에 있어야 하는 것이 아닐까?해당 메서드를 Order로 옮기거나 생성자에 검증을 넣거나 했어야 했다는 생각이 든다
백엔드
・
워밍업클럽
・
벡엔드-code
・
발자국