묻고 답해요
140만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결Practical Testing: 실용적인 테스트 가이드
Integration Test Truncate
안녕하세요! 좋은 강의 잘 들었습니다!!강의 듣고, 테스트 적용해보며 한가지 궁금한게 생겼는데요!테스트 데이터 삭제의 건 입니다!요구사항이 다음과 같다고 한다면, 어떤 방법을 내리실 지 궁금해서요! Test라는 별도의 환경 없음. 로컬 개발환경은 개발DB를 바라보는 상황JPA가 아닌 MyBatisUnit Test가 아닌 integration Test혹시 이런 상황이라면, 테스트를 진행하며 추가되거나 변경된 데이터들을 어떻게 삭제할 수 있을까요 ??더 나아가서는, 성능테스트를 진행한다면 별도의 Test 환경이 없을 때 성능테스트를 진행하며 생긴 대량의 데이터들을 어떻게 원래대로 원복 시킬 수 있을까요 ??감사합니다!
-
미해결Practical Testing: 실용적인 테스트 가이드
readOnly = true 시 jpa 동작관련
readOnly = true시에는 jpa 에서는 단순 cud 는 동작하지만,변경감지는 안된다는 말씀이신가요?
-
미해결Practical Testing: 실용적인 테스트 가이드
자바 익셉션 종류도 외우시나요??
안녕하세요.강의를 수강하다가 문득 의문이 들었는데, 테스트 코드를 작성하실때 어떤 exception을 던질지도 다 알고 계신것 같더라구요! 예를들어, 아메리카노를 0개 주문할때는 IllegalArgumentException을 던진다거나요.... 혹시 이런 익셉션의 종류들도 전부 시간을 들여 외워야 할까요??? 아니면 자연스레 체득되길 기다릴까요??
-
미해결Practical Testing: 실용적인 테스트 가이드
private 메서드를 public으로 바꾸면 어떤가요?
안녕하세요, 우빈님 강의 정말 잘 듣고 있습니다! 한가지 의문점이 드는 것은 private 메서드는 테스트를 하지 않고 그냐 유지하되, 필요 시에 따로 클래스를 분리하여 객체를 만드신다고 했는데요, 그냥 간편히 private 메서드를 public 메서드로 변환해서 내부 호출하는 방법은 별로 고려되지 않은 것 같은데 따로 이유가 있을까요? 혹시 createNextProductNumber()가 ProductService 클래스의 성격과는 맞지 않아서 분리하는 경우인지, createNextProductNumber()가 결국 다른 곳에서 사용 될 수 있이므로 별도의 파일로 관리하는지 궁금합니다
-
미해결Practical Testing: 실용적인 테스트 가이드
서비스단 private 메서드의 책임을 새로운 객체로 분리 시, repository 의존성 이슈에 대해
학습 관련 질문을 남겨주세요. 어떤 부분이 고민인지, 무엇이 문제인지 상세히 작성하면 더 좋아요!먼저 유사한 질문이 있었는지 검색해 보세요.서로 예의를 지키며 존중하는 문화를 만들어가요. 안녕하세요 우빈님! 강의 너무 잘 듣고 있는 컴공과 대학생입니다!!이번 목차에서 말씀해주신 내용을 바탕으로 개발한 프로젝트에 테스트코드를 작성하기 위해 리펙토링을 하던 도중, 의문점이 생겨서 질문 드립니다.강의에서 말씀해 주신 것처럼, 서비스단의 private 메서드가 가지는 책임을 서비스단이 아닌 다른 객체로 책임을 위임하는 것이 더 객체지향적이라 생각해서 저도 똑같은 방식으로 리펙토링을 진행중입니다.그런데, Layered architecture 에서 서비스단이 레포지토리단에 의존하고 있고, 강의에서와 마찬가지로 레포지토리단에 의존하고 있는 private 메서드인 경우에도 이 메서드의 책임을 서비스가 아닌 다른 객체로 위임하는 것이 과연 괜찮은 건지에 대한 의문이 들었습니다.Layered architecture 를 따른다면, 서비스단만이 레포지토리에 대한 의존성을 가게끔 하는 것이 맞지않나 라는 생각이고, 또 레포지토리의 변경이 발생했을때, 변경이 확산되는 범위를 서비스단으로 국한시키는 것이 더 좋은 게 아닐까라고 생각합니다.강사님의 생각이 궁금합니다!! 답변 달아주시면 감사하겠습니다!
-
해결됨Practical Testing: 실용적인 테스트 가이드
안녕하세요 @Autowired, @Mockbean, @Mock, @InjectMocks에 대해 질문 있습니다.
각 어노테이션을 언제 주로 사용하는지에 대해 아래와 같이 정리를 했는데 맞게 정리 한 건지 궁금합니다.@Autowired를 사용하는 경우@ActiveProfiles("test") @SpringBootTest class OrderServiceTest { @Autowired private ProductRepository productRepository; @Autowired private OrderRepository orderRepository; @Autowired private StockRepository stockRepository; @Autowired private OrderService orderService; ... }스프링 컨텍스트에 실제 빈 객체를 등록하고 해당 빈을 사용합니다. 이렇게 실제 빈을 사용하는 테스트라 실제 동작 검증에 있어서 가장 정확성이 높은 테스트 입니다.@Mockbean과 @Autowired를 섞어 사용하는 경우@WebMvcTest(controllers = ProductController.class) class ProductControllerTest { @Autowired private MockMvc mockMvc; @Autowired private ObjectMapper objectMapper; @MockBean private ProductService productService; ... }스프링 컨텍스트에 실제 빈이 아닌 Mock 빈을 등록합니다.컨트롤러 레이어에 대해서만 단위 테스트를 하고 싶은데 컨트롤러가 서비스 레이어에 의존하고 있으니 이러한 의존을 끊기 위해 @MockBean을 사용하는 방식입니다.@Mock과 @InjectMocks@ExtendWith(MockitoExtension.class) class MailServiceTest { @Mock private MailSendClient mailSendClient; @Mock private MailSendHistoryRepository mailSendHistoryRepository; @InjectMocks private MailService mailService; ... }@Mock을 통해 스프링 컨테이너에 등록되지 않는 가짜 객체를 생성합니다. 그리고 @InjectMocks에 사용된 객체에 @Mock을 통해 생성한 가짜 객체를 주입하는 방식입니다.스프링 컨테이너가 필요없는 외부 시스템에 대해 테스트를 할때 진행하는 방식입니다.위 질문 중에 "@Mockbean과 @Autowired를 섞어 사용하는 경우" 코드에 대해 질문이 있습니다. 컨트롤러에 대한 단위 테스트를 하기 위해 @MockBean을 사용하여 서비스에 대한 의존성을 끊는 부분은 이해를 했습니다. 근데 결국 서비스 객체를 가져다 쓰니 서비스가 의존하고 있는 Repository에 대한 부분도 @MockBean을 사용하여 스프링 컨테이너에 Mock을 등록해야 하지 않나 라는 생각이 듭니다.아래 코드에 대해 질문이 있습니다. 실제 스프링 부트를 실행 하면 각 서비스 객체와 Repository 객체 모두 스프링 컨테이너에 등록되어 서비스 레이어쪽에서 Repository레이어에 의존하는 상황입니다. 저는 스프링 컨테이너에 빈들도 잘 등록되고 서로 잘 데이터를 주고 받는지도 테스트를 해야한다고 생각하는데 아래와 같이 @InjectMocks과 @Mock을 사용하면 스프링 컨테이너와 상관이 없어져서 아래와 같은 상황에서 @InjectMocks과 @Mock을 사용하여 테스트 코드를 작성해도 되는지 궁금합니다. @ExtendWith(MockitoExtension.class) class CustomServiceImplTest { @InjectMocks private CustomServiceImpl customServiceImpl; @Mock private BankRepository bankRepository; @Mock private ProductRepository productRepository; @Mock private OrderRepository orderRepository; ... }1번 질문과 같이 정리를 했지만 @Autowired와 @Mockbean 사용에 대해 헷갈리는점이 있어 아래 내용을 정리 했는데 맞게 이해를 한건지 궁금합니다.@Autowired를 테스트 코드에서 사용하는 핵심은 해당 기능을 스프링 컨테이너에 등록하는걸 넘어서 해당 객체의 기능을 실제 사용하겠다 라는 의미다. @Mockbean을 테스트 코드에서 사용하는 핵심 이유는 의존 관계를 끊기 위함이다. 예를 들어 컨트롤러 쪽에서 서비스쪽에 강한 의존관계를 가지고 있어 우선 @Mockbean을 통해 생성한 가짜 객체를 스프링 컨테이너에 등록하고 이 가짜 객체를 컨트롤러쪽에서 의존하고 있는 객체에 넣어줘서 의존관계를 끊는다. 주의할점은 @Mockbean을 통해 생성한 가짜 객체의 기능은 사용하지 않는다. 아래 코드에서 @MockBean을 통해 생성한 ProductService 객체는 가짜 객체이므로 ProductRepository 객체를 못 불러오나요?@WebMvcTest(controllers = ProductController.class) class ProductControllerTest { @Autowired private MockMvc mockMvc; @Autowired private ObjectMapper objectMapper; @MockBean private ProductService productService; ... }
-
미해결Practical Testing: 실용적인 테스트 가이드
외부 라이브러리를 사용하는 코드의 테스트 코드에 대해 질문이 있습니다.
강의를 보고 기존 프로젝트에 작성하지 않았던 테스트 코드를 작성하면서 공부하고 있습니다.도메인 모듈같은 경우 어떠한 의존성도 가지고 있지 않은 POJO 형태로 코드를 작성해서 단위 테스트로 검증할 수 있었습니다.Crawling 모듈의 경우 Jsoup이라는 라이브러리를 사용하여 특정 URL의 HTML을 불러오고, 해당 HTML에서 필요한 정보를 추출하는 코드를 가지고 있습니다.이때, 전체적으로 Mock을 사용하여 테스트를 진행하고자 하였는데 라이브러리의 특성 때문에 stubbing을 작성하는데 불편함을 느꼈습니다.그래서 HTML을 임의로 만들고, HTML을 불러오는 메서드만 stubbing을 적용하려고 하는게 더 효율적이지 않을까 라는 생각이 들었습니다.외부 라이브러리를 사용하는 코드에서 테스트 작성이 미숙한 것 같아서 어떤 방향이 더 좋을지 질문드리고 싶습니다. 아래 두 코드는 동일한 테스트를 진행하고 있는 코드가 아닌점 참고바랍니다.@Mock private lateinit var webPageLoader: WebPageLoader<Document> @InjectMocks private lateinit var dietParser: DietParser fun parse() { // given val mockDocument = mock(Document::class.java) val mockRows = Elements() val row1 = mock(Element::class.java) val row2 = mock(Element::class.java) val row3 = mock(Element::class.java) mockRows.add(row1) mockRows.add(row2) mockRows.add(row3) val columns1 = Elements( mock(Element::class.java).apply { given(text()).willReturn("2024.10.23") }, mock(Element::class.java), mock(Element::class.java), mock(Element::class.java).apply { given(text()).willReturn("Menu 1, Menu 2") } ) val columns2 = Elements( mock(Element::class.java).apply { given(text()).willReturn("교직원식당") } ) val columns3 = Elements( mock(Element::class.java).apply { given(text()).willReturn("2024.10.24") }, mock(Element::class.java), mock(Element::class.java), mock(Element::class.java).apply { given(text()).willReturn("Menu 3, Menu 4") } ) given(webPageLoader.getHTML(anyString())).willReturn(mockDocument) given(mockDocument.select(anyString())).willReturn(mockRows) given(row1.select(anyString())).willReturn(columns1) given(row2.select(anyString())).willReturn(columns2) given(row3.select(anyString())).willReturn(columns3) // when val result = dietParser.parse() // then assertThat(result).hasSize(2) .extracting("date", "menus") .containsExactly( tuple(LocalDate.of(2024, 10, 23), listOf("Menu 1", "Menu 2")), tuple(LocalDate.of(2024, 10, 24), listOf("Menu 3", "Menu 4")), ) } @Mock private lateinit var webPageLoader: WebPageLoader<Document> @InjectMocks private lateinit var parser: DepartmentNoticeParser fun parse() { // given val html = """ <table class="board-table"> <tbody> <tr> <td class="td-num">2</td> <td class="td-subject"><a href="javascript:fnView('2','url','title','2')">공지 제목 2</a></td> <td class="td-write">작성자 2</td> <td class="td-date">2024.10.02</td> </tr> <tr> <td class="td-num">1</td> <td class="td-subject"><a href="javascript:fnView('1','url','title','1')">공지 제목 1</a></td> <td class="td-write">작성자 1</td> <td class="td-date">2024.10.01</td> </tr> </tbody> </table> """.trimIndent() val document: Document = Jsoup.parse(html) given(webPageLoader.getHTML(anyString())).willReturn(document) parser.initialize(Major.COMPUTER_SOFTWARE_ENGINEERING) // when val notices = parser.parse() // then assertThat(notices).hasSize(2) .extracting("number", "title", "author", "date", "url") .containsExactlyInAnyOrder( tuple( 2, "공지 제목 2", "작성자 2", LocalDate.of(2024, 10, 2), "https://www.dongyang.ac.kr/combBbs/2/url/2/view.do?layout=unknown" ), tuple( 1, "공지 제목 1", "작성자 1", LocalDate.of(2024, 10, 1), "https://www.dongyang.ac.kr/combBbs/1/url/1/view.do?layout=unknown" ), ) }
-
미해결Practical Testing: 실용적인 테스트 가이드
테스트 given절 작성 시, "팩토리 메서드를 통한 생성 지양" 관련 문의
학습 관련 질문을 남겨주세요. 어떤 부분이 고민인지, 무엇이 문제인지 상세히 작성하면 더 좋아요!먼저 유사한 질문이 있었는지 검색해 보세요.서로 예의를 지키며 존중하는 문화를 만들어가요. 강의에서 테스트 given절에서 팩토리 메서드를 통한 객체 생성은 어떠한 의도가 들어갈 수 있으므로 지양하라고 하셨습니다. 관련하여 질문이 있습니다. 예를 들어 다음과 같은 팩토리 메서드가 있다고 하면,public class Coffee { private String type; private int size; public Coffee(String type, int size) { this.type = type; this.size = size; } }public class SeasonalCoffeeFactory { public static Coffee createCoffee(String season) { switch (season.toLowerCase()) { case "summer": return new Coffee("Iced Coffee", 50); case "winter": return new Coffee("Hot Coffee", 30); case "spring": return new Coffee("Latte", 40); case "fall": return new Coffee("Pumpkin Spice Latte", 45); default: throw new IllegalArgumentException("Unknown season: " + season); } }}여름 시즌 커피에 대한 테스트 코드는 다음과 같을 겁니다. @Test public void testCreateSummerCoffee() { Coffee coffee = SeasonalCoffeeFactory.createCoffee("summer"); assertEquals("Iced Coffee", coffee.getType()); assertEquals(50, coffee.getSize()); } 이때 테스트 코드는 시즌에 맞는 커피 메뉴를 given절에 생성한 후 타입 일치여부만 판단하면 됩니다.하지만 이 부분을 팩토리 메서드로 작성하지 않게되면커피를 생성함에 있어if ("summer".equalsIgnoreCase(season)) { coffee = new Coffee("Iced Coffee", 50); } else if... 와 같은 부분이 테스트 코드의 given절에 들어가야하지 않나 싶습니다. 물론 이런 부분에 대해서 if라는 논리구조가 들어갔으니 한번 더 테스트 코드 메서드가 분리되어야하는건가 싶기도 한데 확신이 잘 안 서서 질문 드리고 싶습니다.긴 글 읽어주셔서 감사합니다.
-
미해결Practical Testing: 실용적인 테스트 가이드
TDD 를 적용하면 구현하려는 기능에 필요한 객체들을 설계하고 들어가야 하는건가요?
안녕하세요 강사님. 요즘 질문을 많이해서 대단히 죄송하게 생각하고 있습니다! 우선 질문 계기를 들어보시죠. [질문을 하게 된 이유]처음에 최소한으로 돌아가는 쓰레기 코드를 만들었고, 리팩토링으로 객체를 쪼개며 단위 테스트를 작성하고 있었습니다. 근데 갑자기 이런 고민이 들더군요. "TDD 를 적용하려면 어떻게 해야하지?" 하지만 아무리 생각을 해봐도 TDD 를 적용하려면 "쓰레기를 만들고, 여러 객체들로 쪼개 개선해나가는" 형태의 개발방식에은 사용할 수 없다는 결론에 도달하게 되었습니다. 애초에 TDD 를 적용하려면 "처음부터 구현하려는 기능에 필요한 객체들을 생각하고 만들어나가야 하는건가?" 아니 이러면 너무 피곤하지 않나? 라는 생각이 들었습니다. [본격적인 질문][질문 1]처음부터 돌아가는 쓰레기 코드(하나의 클래스에 몰빵) 를 만들고, 리팩토링을 통해 여러 객체로 책임을 분산시키는 형태의 개발 방식은 애초에 TDD 를 적용할 수 없는 것인가요? (TDD 를 적용하면 애초에 스파게티 코드가 나올 수 없는건가요?) [질문 2]TDD 가 애초에 구현하려는 기능에 필요한 객체들을 처음부터 설계하고 들어가는 방식인건가요? [질문 3]TDD 를 적용하려면 구현하는 기능에 필요한 도메인 지식들을 완벽하게 숙지하고 들어가야 코드를 수정하는 일이 별로 없을거라고 생각됩니다. 그렇지 않으면 "일단 쓰레기 구현 -> 리팩토링 -> 테스트" 개발 방식보다 시간이 훨씬 오래 걸릴거라고 생각하는데.. 강사님 생각은 어떠신지 궁금합니다. [질문 4]TDD 를 적용하면 코드 품질이 좋을 수 밖에 없는 이유가 [질문 2] 때문인가요? (한국말 너무 어렵습니다..)
-
미해결Practical Testing: 실용적인 테스트 가이드
contextLoads() 테스트는 왜 깨진 것인가요?
강의 중에 별도의 언급 없이 지우고 넘어가셔서 질문합니다.contextLoads() 테스트는 왜 깨진 것인가요?이 테스트는 지워도 되는 것인가요?
-
미해결Practical Testing: 실용적인 테스트 가이드
'아래 spring batch 통합 테스트시 @Transactional 사용 어려움 질문'의 추가질문입니다.
아래 질문에서 (https://www.inflearn.com/community/questions/939280/spring-batch-%ED%86%B5%ED%95%A9-%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%8B%9C-transactional-%EC%82%AC%EC%9A%A9-%EC%96%B4%EB%A0%A4%EC%9B%80-%EC%A7%88%EB%AC%B8%EC%9D%B4%EC%9A%A5?srsltid=AfmBOorgNyXxuBBsgtD-dGYQq1p884s2u8Wg1u75xaynYq2EooXmmnnM)즉, 스프링 배치 통합 테스트를 작성하는 경우, 현 강의에서 다루고 있는 테스트 작성법과는 다른 접근이 필요한 것이죠. (배치 수행 시 chunk 단위로 여러 번 트랜잭션이 수행될테니까요)답변을 달아주셨는데요,테스트 클래스에 @Transactional 붙이면, 하나의 테스트 메서드 내 스프링 배치의 작업도 일괄 롤백될테니까, 결국엔 테스트 간에 오류를 안일으키지 않나요..?
-
미해결Practical Testing: 실용적인 테스트 가이드
OrderRepositoryTest에서 Product 객체를 생성하는 것을 생략하고 테스트 해도 괜찮나요?
OrderRepository에서 startDateTime, endDateTime, orderStatus를 파라미터로 받아 List<Order>를 반환하는 findOrdersBy 쿼리메소드를 테스트하려고합니다.OrderRepositoryTest 내부에서 해당 테스트를 작성하던 중 given절에서 Product 객체를 생성하는 과정을 생략하고 Order 객체를 생성할때 비어있는 리스트를 이용하여 생성하도록 하였습니다. @ActiveProfiles("test") @SpringBootTest class OrderRepositoryTest { @Autowired private OrderRepository orderRepository; @DisplayName("원하는 기간과 원하는 주문상태의 주문들을 조회한다.") @Test void findOrdersBy() { // given LocalDate registeredTime = LocalDate.of(2024, 10, 24); Order order1 = Order.builder() .products(List.of()) .orderStatus(PAYMENT_COMPLETED) .registeredDateTime(registeredTime.atStartOfDay()) .build(); Order order2 = Order.builder() .products(List.of()) .orderStatus(PAYMENT_COMPLETED) .registeredDateTime(registeredTime.plusDays(1).atStartOfDay()) .build(); orderRepository.saveAll(List.of(order1, order2)); // when List<Order> orders = orderRepository.findOrdersBy( registeredTime.atStartOfDay(), registeredTime.plusDays(1).atStartOfDay(), PAYMENT_COMPLETED); // then assertThat(orders).hasSize(1) .extracting("orderStatus", "registeredDateTime") .contains( tuple(PAYMENT_COMPLETED, registeredTime.atStartOfDay()) ); }이는 테스트하려는 목적이 Order 객체 내부에 적절한 List<OrderProduct> 값을 가지고 있는지를 검증하려는 것이 아니고 findOrdersBy 쿼리메소드가 적절한 Order객체를 가져오는지를 테스트하는 것이라고 생각하였습니다.위와 같이 실제 객체를 생성하는 것 대신 비어있는 리스트 값을 활용하는 것도 괜찮은지 궁금합니다.
-
미해결Practical Testing: 실용적인 테스트 가이드
stub, mock 개념차이
// stubbing when(mailSendClient.sendEmail(any(String.class), any(String.class), any(String.class), any(String.class))) .thenReturn(true);안녕하세요.어떤 기능을 요청해서 어떤 결과를 반환하니 행위검증용도의 목킹이 아닌가요?강의에서는 스터빙이라고 설명하고 있는데, 상태검증보단 행위검증인거같아서요
-
미해결Practical Testing: 실용적인 테스트 가이드
Presentation Layer 테스트 관련 질문 있습니다!
학습 관련 질문을 남겨주세요. 어떤 부분이 고민인지, 무엇이 문제인지 상세히 작성하면 더 좋아요!먼저 유사한 질문이 있었는지 검색해 보세요.서로 예의를 지키며 존중하는 문화를 만들어가요. 현재 Presentation Layer 테스트에서 create할때에는 httpstatus만 확인하는데 보통 Presentation Layer 테스트의 경우 상태값만 확인하고 실제로 db에 저장되어 있는지 확인하지는 않을까요? 만약 확인한다면 통합테스트가 필요할 것 같은데 약간 다른 결의 질문이지만 통합테스트시에 spring Security 인증을 포함해서 해야할까요 아니면 제외하고 해야할까요?만약 제외하고 해야한다면 통합테스트시 spring Security를어떻게 제외해야 하는지도 여쭤보고 싶습니다
-
미해결Practical Testing: 실용적인 테스트 가이드
영속성 계층과 E2E 테스트에 대해 질문이 있습니다.
안녕하세요! 소프트웨어의 안정성과 테스트에 대해서 많은 고민을 가지고 있는 주니어 백엔드 개발자입니다.우빈님의 강의로 실용적인 테스팅 방식에 대해서 기반을 다졌습니다. 덕분에 면접 질문에서도 우빈님의 의견과 저의 의견이 합쳐져서 좋은 답변을 할 수 있었어요!취업 이후에도 여러가지 테스트 서적을 읽으면서 다른 관점도 많이 바라보고 있는데요. 이 과정에서 영속성 계층에서의 테스트와 E2E 테스트 부분에서 해소되지 않은 고민이 있어서 조언을 얻고자 찾아왔습니다.먼저 영속성 테스트 관련 질문부터 드리고자 합니다.취업을 하기 전에 취준을 하는 상황에서는 H2 DB로만 해소 할 수 있는 상황이 많았던 것 같습니다.하지만 실무에 들어오니 생각보다 Native Function을 사용하는 경우가 있고, 버전 문제로 각 DB에 있는 연산자가 제대로 동작하지 않는 일 (Dialect 이슈) 도 적지 않게 볼 수 있었습니다.이전에 답변 주신 https://www.inflearn.com/community/questions/1408867/classicist-vs-mockist해당 내용처럼 저 또한 동일하게 영속성 계층에서의 테스트를 DB 관점에서 그냥 동작하겠거니 하고 안일하게 생각하여 제대로 동작하지 않는 케이스를 봤기에 할 필요가 있다고 생각을 하는데요.아직까지는 제 경험에서는 Test Container를 사용하여 실 상황과 유사한 DB를 사용하는 것이 그나마 합리적인 방법으로 생각을 하고 있습니다. 하지만, 또 컨테이너가 뜨다보니 테스트하는데 걸리는 시간이 상당한 것도 단점으로 다가오기는 하더라구요.명확한 답은 없겠지만 우빈님은 위와 같은 비슷한 상황에서 어떤 방식을 택하고 계시는지 더 좋은 방법은 없을지하여 질문을 먼저 드리게 되었습니다.이어서, E2E 테스트 관련 질문입니다.비즈니스 계층에서의 통합 테스트가 작성이 되었다면 아직까지는 우빈님의 생각과 동일하게 프레젠테이션 영역에서는 통합 테스트를 하지 않아도 괜찮지 않을까? 라는 생각을 가지고 있는데요.하지만, 종종 테스트 관련 여러 서적이나 토론등을 보면 E2E 테스트는 중요하다는 관점을 가지고 있는 글이나 영상을 종종 찾아 볼 수 있었고, 무엇보다 저희 팀원분들중에서도 "E2E 테스트를 하는게 아니라면 프레젠테이션 레이어를 테스트 할 필요가 있어?" 라는 질문을 받았을 때 다음과 같은 생각이 들었습니다."그러네.. 지금까지는 거의 문서화를 목적으로 작성을 했었는데, 문서화를 RestDocs, RestDocs to Swagger(OpenApi Spec) 를 하는게 아니라면 작성 할 필요가 있을까?" 라는 의문이 떠올랐습니다. 혹시 이 부분에 대해서 어떻게 생각하시는지 우빈님의 소견을 듣고 싶어서 긴 글로 질문을 드리게 되었습니다.항상 좋은 강의 좋은 인사이트 제공 해주셔서 늘 감사드립니다.
-
미해결Practical Testing: 실용적인 테스트 가이드
builer 생성 방식 메서드 분리
학습 관련 질문을 남겨주세요. 어떤 부분이 고민인지, 무엇이 문제인지 상세히 작성하면 더 좋아요!먼저 유사한 질문이 있었는지 검색해 보세요.서로 예의를 지키며 존중하는 문화를 만들어가요. 강의를 보면 createProduct 를 통해서 builder 패턴 생성자 생성 방식을 공통메서드로 분리해주셨는데요. 이는 builder 패턴이 제공해주는 생성자 생성방식의 유연성을 함수로 분리함으로써 인해 제약을 주는 것이기도 한 거 같다는 생각이 들었습니다. 만약에 각 Product마다 필요한 컬럼이 다르다면 createProduct 함수로 분리하는게 아닌 반복이 되지만 어쩔 수 없이 각각 builder() + 체이닝 방식으로 코드를 기술하셨을지, 아니면 필요한 함수 시그니처마다 함수를 분리하셨을지 궁금합니다.
-
미해결Practical Testing: 실용적인 테스트 가이드
Classicist VS. Mockist
우빈님. 안녕하세요! 질문이 있어서 남겨드립니다.제가 테스트할때는 repository부분의 쿼리메서드나 jpql로 작성한 코드들은 따로 테스트를 하지 않고 QueryDSL같은 외부 라이브러리를 사용할때만 단위 테스트를 진행합니다. 그리고 비즈니스 레이어에 대해서는 위의 repository를 mocking하여 사용하고 컨트롤러 부분에서 통합테스트를 진행합니다.해당 부분에서 우빈님과 하는 방식이 다른것 같습니다. 우빈님은 비즈니스 레이어에서 통합테스트를 진행하고 Presentation 레이어에서 mocking을 이용한다고 하셨는데 혹시 제가 하는 방식에서 조언을 주실 수 있으실지 잘못된 방향성으로 가고 있는지에 대해 여쭤보고 싶습니다. 또한 우빈님께서 그렇게 진행하시는 이유에 대해 듣고 싶습니다.
-
해결됨Practical Testing: 실용적인 테스트 가이드
테스트 코드에 대해 질문 있습니다.
private String createNextProductNumber() { String latestProductNumber = productRepository.findLatestProductNumber(); if (latestProductNumber == null) { return "001"; } int latestProductNumberInt = Integer.parseInt(latestProductNumber); int nextProductNumberInt = latestProductNumberInt + 1; return String.format("%03d", nextProductNumberInt); } Integer.parseInt(latestProductNumber); 이 부분 처럼 값을 숫자로 바꿀때 latestProductNumber가 숫자인지 아닌지에 대한 검증은 안해도 되는건지 궁금합니다. public OrderResponse createOrder(OrderCreateRequest request, LocalDateTime registeredDateTime) { List<String> productNumbers = request.getProductNumbers(); List<Product> products = findProductsBy(productNumbers); deductStockQuantities(products); Order order = Order.create(products, registeredDateTime); Order savedOrder = orderRepository.save(order); return OrderResponse.of(savedOrder); } ... private static List<String> extractStockProductNumbers(List<Product> products) { return products.stream() .filter(product -> ProductType.containsStockType(product.getType())) .map(Product::getProductNumber) .collect(Collectors.toList()); } private Map<String, Stock> createStockMapBy(List<String> stockProductNumbers) { List<Stock> stocks = stockRepository.findAllByProductNumberIn(stockProductNumbers); return stocks.stream() .collect(Collectors.toMap(Stock::getProductNumber, s -> s)); } private static Map<String, Long> createCountingMapBy(List<String> stockProductNumbers) { return stockProductNumbers.stream() .collect(Collectors.groupingBy(p -> p, Collectors.counting())); } 위 상황처럼 createOrder()메서드가 아래 private 메서드를 모두 호출 하므로 createOrder() 메서드만 테스트하고 나머지 메서드는 테스트를 안 해도 되는건가요?질문의 요지는 private이냐 아니야가 아니라 특정 메서드에서 다른 메서드를 모두 호출한다면 특정 메서드만 테스트를 해도 되는지 아니면 각각의 메서드도 테스트를 해야하는지 궁금합니다.
-
미해결Practical Testing: 실용적인 테스트 가이드
Mock 반환값 질문
프론트랑 협업 하실 때, 서비스 로직을 먼저 만드는게 아닌 반환값을 먼저 준다고 하셨는데(?) 예를 들어 어떻게 주시는지 궁금합니다. 하드코딩으로 그냥 데이터를 임의로 입력해주시는건지 예시를 보여주시면 감사하겠습니다 !컨트롤러, 서비스, 레포지토리에서 어떤식으로 진행되는지 궁금합니다.
-
미해결Practical Testing: 실용적인 테스트 가이드
DTO -> 엔티티, 엔티티 -> DTO 과정에서의 빌더 패턴과 정적 팩토리 메서드 사용에 대해 질문 있습니다.
DTO -> 엔티티, 엔티티 -> DTO 과정에서의 빌더 패턴과 정적 팩토리 사용에 대해 아래와 같이 정리를 했는데 제가 정리한 방식이 타당한 방식인지 궁금합니다. DTO -> 엔티티엔티티의 생성자에 빌더 패턴을 적용합니다.외부에서 엔티티를 생성하려면 빌더 패턴을 사용하여 엔티티를 생성합니다. 예를들어 서비스 레이어에서 엔티티의 빌더 패턴을 사용하여 DTO를 엔티티로 변환하는 작업을 가집니다.엔티티쪽에서 DTO를 엔티티로 변환하는 정적 팩토리 메서드를 가지지 않는 이유 변화가 심한 DTO에 엔티티가 의존하는 것은 좋지 않다고 생각했습니다. 그래서 객체 생성에 빌더 패턴만 적용했습니다. 엔티티 -> DTODTO쪽에서 생성자에 빌더 패턴을 적용합니다.또한 엔티티 -> DTO로 변환하는 정적 팩토리 메서드를 만드는데 이때 DTO 객체 생성은 DTO쪽에서 정의한 빌더 패턴을 적용합니다.DTO는 엔티티에 의존해도 아무 문제가 없다고 생각합니다. 그래서 빌더 패턴도 사용하고 정적 팩토리 메서드도 사용하여 DTO 객체를 생성했습니다.이 둘을 같이 사용할 경우 빌더 패턴과 정적 팩토리 메서드 장점 모두 사용할 수 있다고 생각하여 2가지 모두 사용하여 DTO 객체를 생성하도록 했습니다.