안녕하세요 좋은 강의 덕분에 테스트에 대해 많이 알게 되었습니다 :) 스프링 시큐리티를 포함한 테스트와 관련해서 질문을 드려보고자 글을 쓰게 되었습니다. (강의에서는 스프링 시큐리티를 사용하지 않지만 테스트와 관련해서 물어볼 곳이 마땅치 않아 이 곳에 글을 남기는 점 양해해주시면 감사하겠습니다) @RequiredArgsConstructor @RestController @RequestMapping("/api") @Slf4j public class AuthController { private final AuthService authService; @PostMapping("/auth/sign-up") public String signUp(@RequestBody SignUpRequest request) { authService.signUp(request); return "회원가입 성공"; } } 현재 회원가입을 하는 컨트롤러의 메서드는 다음과 같습니다. @Service @RequiredArgsConstructor @Slf4j @Transactional(readOnly = true) public class AuthService { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; @Transactional public void signUp(SignUpRequest request) { if (userRepository.findByEmail(request.getEmail()).isPresent()) { throw new AlreadyExistException("이미 존재하는 이메일입니다."); } if (userRepository.findByNickname(request.getNickname()).isPresent()) { throw new AlreadyExistException("이미 존재하는 닉네임입니다."); } User user = User.builder() .name(request.getName()) .email(request.getEmail()) .password(request.getPassword()) .nickname(request.getNickname()) .role(UserRole.USER) .build(); user.passwordEncode(passwordEncoder); userRepository.save(user); } } 그리고 실제 회원가입이 진행되는 로직은 이렇습니다. 그리고 제가 WebMvcTest 어노테이션을 사용해서 테스트 코드를 작성한 건 다음과 같습니다. @ActiveProfiles("test") @AutoConfigureMockMvc @WebMvcTest(AuthController.class) class AuthControllerTestWithWebMvcTest { @Autowired protected MockMvc mockMvc; @Autowired protected ObjectMapper objectMapper; @MockBean private AuthService authService; @MockBean private UserRepository userRepository; @DisplayName("회원가입 한다.") @CustomMockUser // 회원가입을 해야되는데 인증이 필요하다? -> 논리적으로 말이 안 됨 -> 올바른 테스트? @Test void signUp() throws Exception { // given SignUpRequest request = SignUpRequest.builder() .name("zun") .email("zun@test.com") .password("12345") .nickname("zunny") .build(); // then mockMvc.perform(post("/api/auth/sign-up") .content(objectMapper.writeValueAsString(request)) .contentType(APPLICATION_JSON) .with(csrf()) ) .andExpect(status().isOk()) .andDo(print()); } } 보시다시피 회원가입 API에 요청이 제대로 되는지를 확인하기 위해 @CustomMockUser 라는 어노테이션을 사용하고 있습니다. (이 어노테이션은 테스트에서 인증을 처리하기 위해 만든 어노테이션입니다. 호돌맨님 강의를 참고해서 만들었습니다.) 제가 혼란스러운 부분은 테스트코드에 써놓은 주석처럼 회원가입이 제대로 되는지를 테스트하기 위한 컨트롤러 코드인데 이걸 통과시키기 위해 인증을 담당하는 @CustomMockUser를 사용하는 것이 올바른 테스트인가 하는 의문입니다. @WebMvcTest를 사용하지 않고 @SpringBootTest를 사용하면 다음처럼 할 수가 있습니다. @ActiveProfiles("test") @AutoConfigureMockMvc @SpringBootTest class AuthControllerTest { @Autowired private UserRepository userRepository; @Autowired private ObjectMapper objectMapper; @Autowired private MockMvc mockMvc; @BeforeEach void tearDown() { userRepository.deleteAllInBatch(); } @DisplayName("회원가입 한다.") @Test void signUp() throws Exception { //given SignUpRequest request = SignUpRequest.builder() .name("zun") .email("zun@test.com") .password("12345") .nickname("zunny") .build(); //expected mockMvc.perform(post("/api/auth/sign-up") .content(objectMapper.writeValueAsString(request)) .contentType(APPLICATION_JSON) ) .andExpect(status().isOk()) .andDo(print()); } } 혹시 이와 관련해서 우빈님은 어떻게 생각하시는지, 그리고 스프링 시큐리티를 사용하는 프로젝트에서는 컨트롤러 쪽 테스트를 어떻게 하는 것이 좋은지 알려주시면 감사하겠습니다!
안녕하세요 강사님, 먼저 좋은 강의 잘 듣고 있어서 감사의 말씀 드립니다. 테스트에 대한 질문은 아니지만 강의 중 작성해주신 코드에 관하여 궁금증이 생겨 질문 드립니다. 만약 문제가 되는 경우에는 삭제하도록 하겠습니다. 먼저 궁금증이 생긴 위치는 다음과 같습니다. Business Layer 테스트 (3) 강의의 31:07 Stock 엔티티의 deductQuantity 메소드에서 예외를 발생시키는 코드 public void deductQuantity(int quantity) { if (isQuantityLessThan(quantity)) { throw new IllegalArgumentException("차감할 재고 수량이 없습니다."); } this.quantity -= quantity; } [질문] 강의 중에서 deductQuantity 메소드에서 왜 isQuantityLessThan로 검증해야 하는지 설명해 주셨는데 그 부분은 이해가 되었습니다. 그런데 예외 메세지를 직접 문자열로 작성해주는 부분에서 궁금증이 생겼습니다. 스프링 빈에서 MessageSource 인터페이스를 통해 별도의 properties 파일로 예외 메세지 관리가 가능한데, 엔티티는 스프링 빈으로 등록되는 것이 아니어서 MessageSource를 통해 예외 메세지를 가져올 수 없을 것이라 생각됩니다. (그러나 deductQuantity가 호출되는 시점에서 진짜로 차감할 재고 수량이 있는지 체크하는 것도 타당하다고 생각합니다.) 그렇다면 서비스에서는 MessageSource를 통해 에러 메세지를 가져오되, 엔티티에서는 문자열로 직접 작성하는 방법밖에 없을까요? (대부분의 메세지는 properties 파일에 작성해두고, 엔티티에서만 직접 문자열로 작성하는 것이 약간 통일되지 않았다는 느낌이 들기도 하는데 어쩔 수 없는 것 같기도 합니다..) 혹시 현업에서도 MessageSource로 메세지 처리가 가능한 부분은 MessageSource를 사용하고, 그렇지 않는 부분에서는 직접 문자열로 작성하는 방식이 많이 사용되는지도 궁금합니다.
안녕하세요. 강사님께서 OrderProduct 클래스를 아래와 같이 작성하셨습니다. Order와 Product는 N:N 양방향 관계인데 이것을 피하기 위해 OrderProduct 클래스를 새로 생성하셨는데요. order 필드가 @ManyToOne이면 product 필드가 @OneToMany가 되어야 하는게 아닌가요? 그래야 N:N 구조가 되는 것 같아서요! @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity public class OrderProduct extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne(fetch = FetchType.LAZY) private Order order; @ManyToOne(fetch = FetchType.LAZY) private Product product; public OrderProduct(Order order, Product product) { this.order = order; this.product = product; } }
안녕하세요, 좋은 강의 잘 듣고 있습니다. 강의 14분쯤에 forDisplay() 메서드를 ProductSellingType Enum 파일에서 생성을 하셨는데요. ProductService 클래스가 아닌 ProductSellingType Enum에서 생성한 이유가 있을까요? 어떠한 기준으로 생성을 하셨는지 궁금합니다. 추가적으로 이런 부분에 있어 특정 기준을 세우는 관련 글?을 읽고 싶은데 키워드 같은게 있을까요?
이런 식으로 코드를 사용중입니다 from PyQt6.QtWidgets import * from PyQt6.QtCore import * class thread (Qthread): def __init__(self): super().__init__() def run(self): while True: mainbot_window.fun1() class UI (QWidget): def __init__(self): super().__init__() self.inits() self.T1 = thread() self.T1.start() def inits(self): print("각종 변수 설정") def fun1(self): print("run code") def main(args=None): global mainbot_window app = QApplication(sys.argv) mainbot_window = UI() mainbot_window.show() try: app.exec() except KeyboardInterrupt: pass if __name__ == '__main__': main() 여기서 global 변수를 사용하지 않고 싶습니다. 글로벌 변수를 사용하지 않고 Thread 에서 Qwidget 의 함수를 사용할 방법이 있을까요? 시도한것 thread 에서 pyqtsignal.emit 을 사용해서 시도해봤는데 ui가 검은 화면으로 뜬체 작동하지않습니다. Qthread 대신 Qtimer 를 사용 =>작동은 잘되나 ui를 표시하는데 약간의 딜레이가 생깁니다. 그래서 되도록이면 Thread를 사용하고싶습니다.
안녕하세요 3강 콘다 가상환경에서 비주얼 스튜디오 터미널에서 conda activate myST해도 터미널에 계속 PS로 표시되며 가상환경으로 들어가지 않습니다. cmd에서는 같은 명령어를 입력하면 들어가지는데 비주얼 스튜디오 터미널에서는 들어가지지 않는 것 같습니다. 뭐가 문제일까요?
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요. 1. 강의 내용과 관련된 질문을 남겨주세요. 2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요. (자주 하는 질문 링크: https://bit.ly/3fX6ygx) 3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요. (질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG) 질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요. ========================================= [질문 템플릿] 1. 강의 내용과 관련된 질문인가요? (예/아니오) 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오) 3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오) [질문 내용] 여기에 질문 내용을 남겨주세요. 코드를 모두 정상적으로 작성하였는데 Cannot snapshot C:\Users\이름\OneDrive\바탕 화면\App\Programming\study\inflearn\Spring\forBeginner\hello-spring\build\test-results\test\binary\output.bin: not a regular file 이런 오류가 뜹니다.
[질문 템플릿] 1. 강의 내용과 관련된 질문인가요? (아니오) 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예) 3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예) [질문 내용] 안녕하세요. 강사님의 강의를 듣고 Spring Boot로 웹 페이지 개발을 연습중입니다. TDD방식으로 구현을 하며 웹 페이지를 만들고 있습니다. 환경 IDE: IntelliJ pc: MacBook Air 2022 Spring Boot Version: 3.1.2 소스파일: https://drive.google.com/file/d/17oeGgTCTbRMAtcaxrE4sWDtw7NRTJlPk/view?usp=sharing 구현 테스트환경을 독립적으로 구성하고 싶어서 @BeforeEach를 사용하여 테스트 코드 실행 전에 H2 db 인메모리에 sql스크립트를 실행 하여 데이터를 세팅하는 작업을 하고 테스트들을 진행 중에 있습니다. import org.springframework.jdbc.core.JdbcTemplate; import org.junit.jupiter.api.BeforeEach; import org.springframework.core.io.ClassPathResource; import org.springframework.jdbc.datasource.DriverManagerDataSource; import org.springframework.jdbc.datasource.init.ScriptUtils; @BeforeEach void dbConfig() { JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); ClassPathResource schema = new ClassPathResource("schema.sql"); try { ScriptUtils.executeSqlScript(jdbcTemplate.getDataSource().getConnection(), schema); } catch (SQLException e) { throw new RuntimeException(e); } ClassPathResource data = new ClassPathResource("data.sql"); try { ScriptUtils.executeSqlScript(jdbcTemplate.getDataSource().getConnection(), data); } catch (SQLException e) { throw new RuntimeException(e); } } 증상 데이터를 Insert후에 반환된 기본 키 값으로 Select하여 검증을 하는 로직에서 단독 테스트시에는 정상적으로 잘 동작하나 test 패키지를 전체 테스트를 돌리면 Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "board" not found; SQL statement: 이와 같이 테이블을 찾을 수 없다고 간헐적 으로 오류가 발생하고 있습니다. [시도해본 방법] SQL스크립트가 제대로 로드 되지 않았다고 생각되어 테이블 생성 스크립트 후에 Thread.sleep(); 을 줘서 지연을 줘봤지만 실패 기존에는 ScriptUtils.executeSqlScript(jdbcTemplate.getDataSource().getConnection(), schema); 실행을 schema와 data를 연달아 수행하던 것을 schem먼저 수행 후, data수행하도록 변경했지만 실패 Chat-GPT에 질문을 해보니 @BeforeEach를 사용하여 데이터베이스 스키마를 초기화하고 데이터를 삽입하는 경우, 테스트 메소드 간에 순서가 보장되지 않을 수 있기 때문에, 특정 테스트 메소드가 실행되기 전에 데이터베이스 초기화가 이루어지지 않을 수 있습니다. 라고 답변을 받았는데요. 그래서 @Order 어노테이션으로 @Test 메소드들의 순서를 지정해봤는데도 동일한 오류가 발생합니다. 이와 같이 잘 안되고 있어 문의 드립니다.
https://www.inflearn.com/questions/789381/%EC%97%94%ED%8B%B0%ED%8B%B0-%EB%A7%A4%EB%8B%88%EC%A0%80-%EB%8F%99%EC%8B%9C%EC%84%B1 위에 질문과 답변을 봤을때는 엔티티매니저가 달라도 트랜잭션이 같으면 같은 영속성컨텍스트를 사용한다고 되어있습니다. 하지만 이 부분이 실제로 어떻게 이뤄질 수 있는것인지 이해되지 않습니다. 코드로 어떻게 되어져 있는지 찾아볼 수 있을까요? 제가 찾아본 내용으로는 SessionImpl이라는 엔티티매니저 구현체에 아래 처럼 엔티티매니저를 넣어서 영속성컨텍스트를 생성하는 코드를 볼 수 있었습니다. 그럼 영속성 컨텍스트는 따로 만들어지는게 아닌가? 라는 생각도 하게되었습니다. protected StatefulPersistenceContext createPersistenceContext() { return new StatefulPersistenceContext( this ); } 트랜잭션에 따라 영속성 컨텍스트가 공유되고 스레드마다 영속성 컨텍스트가 어떻게 나눠질 수 있는지 궁금합니다. 그리고 영속성컨텍스트는 프록시라도 Bean으로 등록되는데 실제 동작을 하는건 원본객체일테고 그럼 그 객체이 있는 영속성컨텍스트를 공유하게 되는것은 아닌가? 의문이 들었습니다. 질문을 정리하겠습니다. 트랜잭션에 따라 영속성 컨텍스트가 어떻게 공유될 수 있나요?(확인할 수 있는코드나 기술이 있다면 말씀해주시면 감사하겠습니다.) Bean으로 등록된 엔티티매니저가 다른 상태를 유지할 수 있는 방법이 무엇인가요? 프록시라해도 원본객체를 통해 동작하는게 아닌가요? 영속성컨텍스트는 프록시라도 Bean으로 등록되는데 실제 동작을 하는건 원본객체일테고 그럼 그 객체이 있는 영속성컨텍스트를 공유하게 되는것은 아닌가요? 바보같은 질문일 수 있지만 답변해주시면 너무 감사하겠습니다.
EntityManager는 빈 스코프가 프록시로 설정된다고 알고있습니다. 하지만 결국은 원본 엔티티매니저를 사용하는것이라고 이해했습니다. 그러면 요청이 들어오면 엔티티매니저를 사용하는데 엔티티매니저 내에 영속성 컨텍스트가 있고 싱글톤으로 유지된다면 영속성컨텍스트가 다른 요청이랑 공유되는게 아닌가요? 싱글톤으로 등록되어 있는데 영속성컨텍스트가 공유되지 않는 이유가 궁금합니다. 엔티티매니저가 빈인데 어떻게 요청마다 생성되는것인지 궁금합니다.
안녕하세요? 강의를 듣다가 궁금한 것이 생겨 질문 드립니다. 자바의 정렬 기본 알고리즘 시간 복잡도와 이진 탐색 시간 복잡도가 nlogn인 건 이해했는데, 코드부를 보면 이중 반복문이 나오고 있습니다. 앞 서 강의에서 반복문을 기준으로 이중 반복문이면 n의2승이라고 말씀하셨는데, 이 중 반복문을 썼는데도 nlogn이 되는 건 반복문이 진행되는 동안 절반씩 찾기 때문인가요?? 만약 이중 반복문으로 시간 복잡도가 올라간다면 이중 반복문을 쓰지 않고, 해결하는 방법을 알려주실 수 있으실까요?
1. interface클래스내에 정의된 메써드는 public abstract이고, 변수는 public static final이 자동으로 앞에 정의가 된다고 하셨는데 이건 고정인건가요? 다른 형태로 봐꿀 수 있나요? 봐꿀 수 없다면 왜 그러한지가 궁금합니다. interface 클래스를 implements해주고 추상클래스를 extends 해주는 것은 둘 다 상속인건가요? 둘다 부모 클래스가 되는건가요?
지금 OrderServiceTest 클래스의 createOrder(), createOrderWithStock(), ... 메서드들은 주문을 생성하기 위해서 given절에서 모두 동일하게 product1, 2, 3을 먼저 생성해주는 fixture가 있습니다. @Test void createOrder() { // given LocalDateTime registeredDateTime = LocalDateTime.now(); Product product1 = createProduct(HANDMADE, "001", 1000); Product product2 = createProduct(HANDMADE, "002", 3000); Product product3 = createProduct(HANDMADE, "003", 5000); productRepository.saveAll(List.of(product1, product2, product3)); OrderCreateServiceRequest request = OrderCreateServiceRequest.builder() .productNumbers(List.of("001", "002")) .build(); // when OrderResponse orderResponse = orderService.createOrder(request, registeredDateTime); // then assertThat(orderResponse.getId()).isNotNull(); assertThat(orderResponse) .extracting("registeredDateTime", "totalPrice") .contains(registeredDateTime, 4000); assertThat(orderResponse.getProducts()).hasSize(2) .extracting("productNumber", "price") .containsExactlyInAnyOrder( tuple("001", 1000), tuple("002", 3000) ); } product를 생성해주는 부분을 @BeforeEach로 빼내려고 하는데, 적절하다고 판단되시는지 궁금합니다. OrderServiceTest의 모든 메서드가 동일한 product를 생성하고는 있지만, 각 테스트 메서드에서 "001"이라는 상품이 있는지는 @BeforeEach 메서드까지 스크롤을 왔다갔다하면서 확인해야될거라 생각합니다. (만약 없는 상품을 주문한다면 given절에서 예외가 발생하는 것이기 때문에, 이 부분은 당연히 있는 상품을 주문한다는 것을 보장 하고 테스트 코드를 작성해야 할까요?) order에 관한 테스트이기 때문에, product에 대한 부분은 분리해도 될거라 생각하지만, 우빈님의 말씀대로 스크롤을 왔다갔다하면서 @BeforeEach의 메서드를 수시로 보는 것은 안좋다고 느껴져서.. product 생성에 대한 fixture를 분리괜찮다고 생각하시나요?
@RunWith(SpringRunner.class) @SpringBootTest public class MemberRepositoryTest { @Autowired MemberRepository memberRepository; @Test @Transactional @Rollback(false) public void testMember() throws Exception { //given Member member = new Member(); member.setUsername("memberA"); //when Long saveId = memberRepository.save(member); Member findMember = memberRepository.find(saveId); //then assertThat(findMember.getId()).isEqualTo(member.getId()); assertThat(findMember.getUsername()).isEqualTo(member.getUsername()); assertThat(member).isEqualTo(findMember); System.out.println("findMember == member: " + (findMember == member)); } } 위 코드에서 디비에 넣은 멤버와 찾은 멤버가 같은지를 비교를 isEqualTo를 사용을 하셨는데 이것은 제가 알기론 value부분만 같다면 true라는것으로 알고 있습니다. 같은 영속성 컨텍스트 안에서의 객체가 같은지 확인하려면 isSameAs가 맞지 않은지 의문이 생겼습니다!