묻고 답해요
156만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결Practical Testing: 실용적인 테스트 가이드
DTO 검증 필드에 대한 테스트 코드 작성은 어디까지?
DTO의 검증 필드마다 테스트코드를 작성하는게 실무에서 일반적인가요? 이렇게 되면 DTO가 커질 수록 DTO 한개당 테스트 함수가 10~11개 이렇게 필드개수 만큼 나오게 될텐데 실제로 모두 테스트코드로 검증하나요?
-
미해결Practical Testing: 실용적인 테스트 가이드
OrderCreateRequest DTO에 대해서 궁금한점
학습 관련 질문을 남겨주세요. 어떤 부분이 고민인지, 무엇이 문제인지 상세히 작성하면 더 좋아요!먼저 유사한 질문이 있었는지 검색해 보세요.서로 예의를 지키며 존중하는 문화를 만들어가요. Business Layer 테스트(1)의 13:32초 즈음에서 OrderCreateRequest가 Product의 id값이 아니라 productNumber를 요청 dto로 받았는데 그 이유가 있나요? 저라면 id를 받도록 설계할거같은데 이유가 궁금합니다. 실무에서는 보통 저렇게하나요?
-
해결됨Practical Testing: 실용적인 테스트 가이드
고전파의 테스트 대역 사용 대상, 공유 의존성
안녕하세요복습을 진행하면서 단위 테스트(블라디미르 코리코프)를 같이 공부하는데, gpt와 씨름해 보아도 모르겠어서 질문 드립니다 ㅠㅠ책에 따르면 고전파의 테스트 대역 사용 대상은 공유 의존성으로 유일하고, 이것의 예로 데이터베이스를 들고 있는데요.우빈님의 강의에 따르면 이것은 고전파의 방식과는 거리가 멀어 보여서 혼란이 옵니다테스트 대역을 쓰고 싶다면, 공유 의존성(데이터베이스)은 가능하다라는 뜻 인걸까요?만약 그렇다면, 고전파가 테스트 대역 사용에 엄격한 방식이라고 이해했었는데, 데이터베이스를 유일한 모킹 가능성 영역이라고 보는 것이 납득하기 어렵습니다강의에서 가르쳐주신 것처럼 외부 서비스(메일)을 모킹 처리 하는 것이 더 나은 방식, 혹은 고전파 다운 방식이라고 생각되어서 혼란스럽습니다..
-
미해결Practical Testing: 실용적인 테스트 가이드
계층 관련 질문이 있습니다.
위 강의에서는 Response 부분을 service 레이어에 response 패키지 안에 관리를 하고 있습니다.개인적인 생각으로는 요청을 받는 부분은 최상단 Presentation Layer 이고 궁극적으로 어떠한 결과를 return 해주는 것 또한 Presentation Layer 이라고 생각합니다. 그렇기에 requestDto 는 controller 쪽에 있는게 맞고 responseDto 또한 controller 쪽에 있어야 하는게 맞지 않을까 하는 생각이 듭니다!혹시 위 부분 어떻게 생각하시는지 궁금합니다.
-
해결됨Practical Testing: 실용적인 테스트 가이드
'코틀린'에서는 빌더를 따로 쓰지 않는데, 이 때는 어떻게 test fixture를 만드시는지 궁금합니다
우빈님 안녕하세요! '코틀린'에서는 빌더를 따로 쓰지 않는데, 이 때는 어떻게 test fixture를 만드시는지 궁금합니다 ㅎㅎ저도 그동안 실무에서 자바를 쓰면서 테스트에서 given 절의 test fixture는 빌더로 만들어서 사용하고, 프로덕션에서는 팩토리 메서드로 객체를 생성해왔습니다. (물론 이 강의를 보고나서부터입니다 ㅎㅎ) 그런데 코틀린을 쓰다보니까 롬복을 거의 쓸 일이 없는 것 같더라고요.private constructor & 팩토리 메서드를 코틀린에서 쓰다 보니까 팩토리 메서드로만 객체를 생성해야해서 테스트에서 '맥락을 이해하는데 허들이 하나 더' 생기는 꼴이 되고 있어 조금 고민입니다.. 그렇다고 테스트 전용 팩토리 메서드 같은 것을 만들자니 프로덕션에 테스트 관련된 소스가 바로 생기는 것은 영 석연치 않더라고요.그래서 지금은 일단 생성자를 public으로 열어두고, 팩토리 메서드가 있는 도메인 객체를 만들 때는 무조건 팩토리 메서드만 쓴다. 그리고 테스트에서는 테스트 용이성을 위해 생성자로 객체를 생성하는 것을 허용하는 것으로 해보려고 합니다. 혹시 위 같은 상황에서 우빈님은 어떤 기준으로 쓰고 계신지 궁금합니다..!바쁘실텐데도 시간 내어 확인해주셔서 감사합니다 :)
-
미해결Practical Testing: 실용적인 테스트 가이드
혹시 update 로직은 어떻게 테스트하나요? (@Setter?)
강의에선 update 로직이 없어서 테스트할 필요는 없는 것 같은데, 필요한 경우에 엔티티에 @Setter 추가해놓고 테스트 하면 될까요?setter 사용을 지양하라는 글이 많은데, 현업에서는 어떤식으로 테스트하나요?
-
미해결Practical Testing: 실용적인 테스트 가이드
단위테스트와 통합테스트의 경계가 궁금합니다.
안녕하세요 우빈님 강의 너무나 잘 듣고 있습니다 🙂주니어에게 큰 힘이 되고 있습니다 감사합니다 공부를 하다보니 단위테스트와 통합테스트의 경계가 궁금하게 되었습니다.우빈님은 컨트롤러 / 서비스 / 리포지토리 각 계층에 대해 단위 / 통합 / 단위 테스트라고 구분하셨는데요 어떤 분은 컨트롤러 계층에서 작성한 테스트에 대해 통합테스트라고 주장하셨습니다@WebMvcTest 또는 @SpringBootTest를 사용해 스프링 컨텍스트의 일부(웹 계층) 또는 전체를 로드해야 한다.진정한 단위 테스트"는 스프링이나 다른 컨테이너 없이 new 연산자를 사용하여 객체를 인스턴스화하고 테스트하는 것을 의미한다고 하셨습니다. 테스트 대상 범위: 또한 테스트는 특정 컨트롤러 메서드의 로직만을 격리하여 테스트하는 것이 아니라, 특정 URL에 대한 HTTP 요청이 스프링 웹 계층을 통과하여 컨트롤러에 도달하고, 컨트롤러의 응답이 HTTP 응답으로 변환되어 반환되는 전체 과정 중 일부를 시뮬레이션하고 검증하는 관점에서 통합테스트다라고 주장하셨습니다. 정확한 구분은 어떻게 해야할까요? 사실 이러한 구분이 중요한가 고민도 됩니다. 어차피 테스트에 대한 목적과 경계 설정을 구분하는 것이 중요하지 않은가 싶긴 한데 우빈님 의견이 궁금합니다.
-
미해결Practical Testing: 실용적인 테스트 가이드
Service+Repository 통합테스트 관련 질문입니다.
요즘 서비스 계층 단위테스트를 위해 모킹과 fake 객체 구현을 공부하고 있습니다. 하지만 레포지토리를 일일이 모킹하는 코드를 작성하는 것이 빠른 피드백이 장점인 단위테스트로 의미가 있는지 의문이 들 정도로 시간이 많이 들더라고요. 그래서, 좀 더 효율적인 강사님의 방식을 따라가고 싶어 강의를 듣던 중 의문이 생겨서 질문드립니다.1. 계층별 테스트 분리 기준에 대한 질문입니다.컨트롤러, 레포지토리는 단위테스트, 서비스 계층은 레포지토리 부분과 통합테스트 이렇게 분리해서 진행하셨던 이유를 여쭤봐도 될까요? 서비스, 컨트롤러, 레포지토리 계층 각각 단위테스트를 작성하고 컨트롤러에서 레포지토리까지 한번 통합테스트를 작성하는 방법도 있을 것 같고, 묶어볼 방법은 몇 가지 더 있는 것 같습니다.그런데 강의에서처럼 분리했던 게 가장 효율적이라고 생각하는 기준과 이유…. 이 분리 방식의 발견 과정이 너무 궁금하네요2. 통합테스트 DB 관련 질문입니다.서비스계층과 레포지토리 계층을 묶은 상태로 H2 같은 임베디드 데이터베이스를 사용하면 테스트 속도가 상당히 느리게 나오긴 합니다. 이런 부분은 어쩔 수 없이 안고 가는 것인가요? 그리고 운영이나 개발 DB를 postgress같이 H2말고 다른 걸 사용한다고 해도, H2로 통합테스트 테스트하는게 이점이 있을까요?3. 서비스 계층 단위테스트 관련 질문입니다.혹시, 부담이 안 된다면, 서비스 계층의 단위테스트가 중요도가 많이 떨어진다고 생각하시는 이유가 Fake든 Mockito를 사용한 Stub이든 데이터베이스를 흉내만 내는 테스트가 의미가 없다고 여기셔서 그런 것일까요?부족한 질문 읽어주셔서 감사합니다!
-
미해결Practical Testing: 실용적인 테스트 가이드
OrderControllerDocsTest 작성 해봤는데요. 날짜 형식이 이상하게 나와요
OrderControllerDocsTest.java@DisplayName("주문 생성 API") @Test void createOrder() throws Exception { OrderCreateRequest request = OrderCreateRequest.builder() .productNumbers(List.of("001")) .build(); LocalDateTime now = LocalDateTime.now(); given(orderService.createOrder(any(OrderCreateServiceRequest.class), any(LocalDateTime.class))) .willReturn(OrderResponse.builder() .id(1L) .totalPrice(4000) .registeredDateTime(now) .products(List.of(ProductResponse.builder() .id(1L) .productNumber("001") .type(ProductType.HANDMADE) .sellingStatus(ProductSellingStatus.SELLING) .name("아메리카노") .price(4000) .build())) .build()); mockMvc.perform(post("/api/v1/orders/new") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andDo(print()) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value("200")) .andExpect(jsonPath("$.message").value("OK")) .andExpect(jsonPath("$.status").value("OK")) .andDo(document("order-create", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestFields( fieldWithPath("productNumbers").type(JsonFieldType.ARRAY) .description("상품 번호") ), responseFields( fieldWithPath("code").type(JsonFieldType.NUMBER) .description("코드"), fieldWithPath("status").type(JsonFieldType.STRING) .description("상태"), fieldWithPath("message").type(JsonFieldType.STRING) .description("메시지"), fieldWithPath("data").type(JsonFieldType.OBJECT) .description("응답 데이터"), fieldWithPath("data.id").type(JsonFieldType.NUMBER) .description("주문 ID"), fieldWithPath("data.totalPrice").type(JsonFieldType.NUMBER) .description("주문 총 금액"), fieldWithPath("data.registeredDateTime").type(JsonFieldType.ARRAY) .description("주문 시각"), fieldWithPath("data.products").type(JsonFieldType.ARRAY) .description("주문 상품"), fieldWithPath("data.products[].id").type(JsonFieldType.NUMBER) .description("상품 ID"), fieldWithPath("data.products[].productNumber").type(JsonFieldType.STRING) .description("상품 번호"), fieldWithPath("data.products[].type").type(JsonFieldType.STRING) .description("상품 타입"), fieldWithPath("data.products[].sellingStatus").type(JsonFieldType.STRING) .description("상품 상태"), fieldWithPath("data.products[].name").type(JsonFieldType.STRING) .description("상품 이름"), fieldWithPath("data.products[].price").type(JsonFieldType.NUMBER) .description("상품 가격") ))); } docs/index.html 에서 확인한 registeredDateTime처음에 테스트 코드 작성시에 ieldWithPath("data.registeredDateTime").type(JsonFieldType.ARRAY) .description("주문 시각"),이 부분을 JsontFieldType.STRING 으로 했더니 테스트 실패 메시지에 해당 타입이 Array 라고 해서 바꿨는데... 문서에 저렇게 나옵니다. 이게 맞는건지 궁금합니다.
-
미해결Practical Testing: 실용적인 테스트 가이드
test 용 .yml
안녕하세요 좋은 강의 감사합니다. 강의에서는 테스트에 사용되는 설정 파일을 main/resources/~.yml 파일을 사용하셨는데요.혹시 test 패키지에 별도의 .yml 파일을 둬서 사용하는 것은 어떻게 생각하시나요?보통 어떻게 하는 것이 올바르고? 장단이 있을지 궁금합니다. 강사님 의견도 궁금하구요! 감사합니다.
-
미해결Practical Testing: 실용적인 테스트 가이드
throws Exception
안녕하세요 좋은 강의 감사합니다. 테스트마다 보통 throws Exception 으로 처리하는 경우가 많은데, 별도로 checked exception 에 대해서는 신경을 안 써도 되는걸까요? 강사님의 의견이 궁금합니다! 감사합니다.
-
미해결Practical Testing: 실용적인 테스트 가이드
카페키오스크 클래스 문의 ,,
학습 관련 질문을 남겨주세요. 어떤 부분이 고민인지, 무엇이 문제인지 상세히 작성하면 더 좋아요!먼저 유사한 질문이 있었는지 검색해 보세요.서로 예의를 지키며 존중하는 문화를 만들어가요. CafeKiosk 클래스 의 add를 아래처럼 구성 했습니다.public void add(Beverage beverage, int count) { if(count <= 0){ throw new IllegalArgumentException("음료는 1잔 이상 주문 하실 수 있습니다."); } for(int i = 0; i<count; i++){ beverages.add(beverage); } } 3:54 초에 add에 americano 변수 1개만 들어 가는데 강의에는 오류가 안나네요..?저는 count 값도 입력하라고 나옵니다.
-
미해결Practical Testing: 실용적인 테스트 가이드
Rest docs 문서용 테스트코드를 따로 작성해야 되나요?
ProductControllerDocsTest 를 따로 작성하지 않고 기존에 작성했던 ProductControllerTest 에 Rest Docs 용 코드를 얹어서 작성해도 상관없나요?
-
미해결Practical Testing: 실용적인 테스트 가이드
테스트 코드에서 필요한 생성자
안녕하세요 좋은 강의 감사합니다. 잘 듣고 있습니다. 두 가지 궁금한 것이 있습니다.테스트 코드에서만 필요한 생성자가 있다면, 그 생성자를 위해서 해당 객체나 엔티티에 생성자를 추가하는 과정이 옳은 것일까요? 아니면 애초에 그런 생성자가 필요한 상황 자체가 잘못된 것일까요? (프로덕션 코드를 테스트하는 것이 목적이니, 프로덕션 코드에서 이미 사용되고 있는 생성자를 쓰면 되지 않냐..) 모 회사에서는 @Builder 패턴은 무조건 restdocs 를 생성하는 경우에만(fixtures) 사용하라고 해서 프로덕션 코드에서는 무조건 생성자만 사용한 경우가 있었는데 @Builder 패턴의 사용 범위? 그런 것들은 어떻게 생각하시나요!? 강사님의 의견이 궁금합니다. 좋은 강의 감사합니다.
-
미해결Practical Testing: 실용적인 테스트 가이드
tearDown 순서
안녕하세요 강의 중반에OrderServiceTest 할 때 작성하셨떤 tearDown() 에서 @AfterEach void tearDown() { orderProductRepository.deleteAllInBatch(); productRepository.deleteAllInBatch(); orderRepository.deleteAllInBatch(); } 위의 순서를 지키지 않으면 왜 테스트가 깨지나요?어차피 매번 수행한 후에 데이터를 다 지우는 것은 동일하잖아요. fk 때문에 지워지지 않는 이유에서 그런건가요.
-
미해결Practical Testing: 실용적인 테스트 가이드
@Builder 생성자 private
안녕하세요 좋은 강의 잘 듣고 있습니다. 다름이 아니라 Product 에서 생성자에 @Builder를 붙이시고 private 으로 선언한 이유에 대해서 알 수 있을까요? 결국 테스트에서도 @Builder 로 바로 사용하듯이 밖에서도 @Builder 를 사용할 수 있으니 private 으로 하는 어떤 의미가 있는지 궁금해서입니다 뭔가 의도로는, @Builder 생성자를 내부에서만 사용할 수 있게 private 으로 하고 of 와 같은 정적 생성자에서만 build()를 사용해서 밖에서는 정적 생성자로만 객체를 생성하도록 하는 것으로 생각했거든요. 미리 감사합니다.
-
미해결Practical Testing: 실용적인 테스트 가이드
@DisplayName gradle / intellJ
안녕하세요 좋은 강의 감사합니다. 'DisplayName 을 섬세하게' 강의에서요! 설정 > Run tests using > gradle(-> intellJ) 로 변경하면 displayName 으로 뜬다고 하셨는데 기존의 gradle 로 하더라도 동일한 것 같은데 뭐가 다른가요? 감사합니다.
-
미해결Practical Testing: 실용적인 테스트 가이드
36:40 초 부근에 tearDown 없어도 통과하는 이유가 궁금해요
@DisplayName("신규 상품을 등록한다. 상품번호는 가장 최근 상품의 상품번호에서 1 증가한 값이다.") @Test void createProduct() { // given Product product = createProduct("001", HANDMADE, SELLING, "아메리카노", 4000); productRepository.saveAll(List.of(product)); ProductCreateRequest request = ProductCreateRequest.builder() .type(HANDMADE) .sellingStatus(SELLING) .name("카푸치노") .price(5000) .build(); // when ProductResponse productResponse = productService.createProduct(request); // then assertThat(productResponse) .extracting("productNumber", "type", "sellingStatus", "name", "price") .contains("002", HANDMADE, SELLING, "카푸치노", 5000); } @DisplayName("상품이 하나도 없는 경우 상품을 등록하면 상품번호는 001 이다.") @Test void createProductWhenProductIsEmpty() { // given ProductCreateRequest request = ProductCreateRequest.builder() .type(HANDMADE) .sellingStatus(SELLING) .name("카푸치노") .price(5000) .build(); // when ProductResponse productResponse = productService.createProduct(request); // then assertThat(productResponse) .extracting("productNumber", "type", "sellingStatus", "name", "price") .contains("001", HANDMADE, SELLING, "카푸치노", 5000); } private Product createProduct(String productNumber, ProductType type, ProductSellingStatus sellingStatus, String name, int price) { return Product .builder() .productNumber(productNumber) .type(type) .sellingStatus(sellingStatus) .name(name) .price(price) .build(); }강사님 화면에서는 tearDown 메소드를 추가해야지 두 메소드 모두 통과하는 것으로 나오는데, 저는 tearDown 메소드 작성 전에도 둘다 통과로 나오던데.. 뭐가 잘못된걸까요??
-
미해결Practical Testing: 실용적인 테스트 가이드
Controller / Service 분리
안녕하세요 선생님 강의 잘들었습니다.! 강의 이후 복습 하며 사이드 프로젝트를 하며 성장 하고 있습니다.프로젝트를 하던중 궁금증이 생겨서 블로그 글을 찾아 보았지만 관련된 글을 찾지 못하여 질문을 드립니다.회원 관련된 API = UserController -> UserService의 흐름으로 사용 하였습니다.하지만 User API가 너무 많아져 특정 API를 수정할 때 찾기 어려워졌습니다.그레서 이걸 분리 할수 없을까에 대한 고민이 생겼고 행위에 따라 분리 했습니다. [ Controller 분리 ]검증 관련한 API를 VerficationController으로 모았습니다.VerficationController에서 필요한 자원에 따라 UserService, EventService를 사용 하였습니다. [ 질문 ]1. Controller를 행위에 따라 부분적으로 분리 하여도 괜찮나요?UserController / EventController / VerificationController분리 했을때 통일성이 없다는 느낌이 들어서 고민 입니다. Controller / Service 책임을 어떻게 설정 하시나요? 회원 관련된 요청이면 UserController 회원 로직이면 UserService를 사용 했습니다.하지만 API가 너무 많아 분리를 할때 어떤 기준으로 분리를 해야 할지 모르겠습니다.
-
미해결Practical Testing: 실용적인 테스트 가이드
CQRS에 대한 jpa interface에 대한 궁금증..
학습 관련 질문을 남겨주세요. 어떤 부분이 고민인지, 무엇이 문제인지 상세히 작성하면 더 좋아요!먼저 유사한 질문이 있었는지 검색해 보세요.서로 예의를 지키며 존중하는 문화를 만들어가요. 안녕하세요 우빈님!테스트 강의 복습 중인데, 다시 들으니깐 너무 재밌네요... 다름 아니라, CQRS 에 따라서 강의를 진행하면서도 Controller와 Service를 CQRS에 따라 분리하고 있습니다.(패키지가 많아지긴하네요..ㅎㅎ..)그런데 궁금한 부분은 Repsitory쪽인데,JpaRepository를 상속받는 인터페이스들(강의로 예를 들면 ProductRepository, StockRepository, OrderRepository가 있겠네요)같은 경우인데, sprind data jpa 인터페이스에 CRUD 중에 CD 에 대한 책임을 줄 수는 있겠다 생각했고,U 는 변경감지에 책임을 맡기면 되겠다고 생각했습니다.. 그런데, 쿼리메서드 부분이 모호한데요.작성했던 쿼리 메서드 같은 경우는 Query에 대한 부분인데 책임을 분리하려고보니, command에 query가 묶여있는 형태더라고요.. 이 부분은 어떻게 나누는지가 궁금합니다..제 짧은 지식 선에서는 QueryDSL 로 나눠야 하나 싶지만, 쿼리 메서드라는 편리한 것이 있는데 굳이 돌아가는 것 같다는 느낌이 드네요제가 굳이 나누려는건지 싶기도 하구요...조언을 구해봅니다..