문자열을 잘라서 배열에 넣는다
빈 리스트에 위의 문자가 없다면 문자를 추가하고, -1을 반환
리스트에 위의 문자가 있다면, 그 문자의 위치를 얻어오고 나서 리스트 안에 있는 문자를 의미없는(aa)로 바꿔준 뒤, 리스트에 찾아온 문자를 넣어준다.
단위 테스트 + JUnit5 + 테스트 기법
단위 테스트 : 작은 단위로 쪼개서 정확하게 동작하는지 확인하는 테스트
myselectshop 말고 JUnit5-practice에서 연습
@BeforeEach : "각각의 테스트 코드가 실행되기 전에 수행되는 메서드"
@AfterEach : "각각의 테스트 코드가 실행된 후에 수행"
@BeforeAll : "모든 테스트 코드가 실행되기 전에 최초로 수행"
@AfterAll : "모든 테스트 코드가 실행된 후에 마지막으로 수행"
@Test : 테스트 코드
실행하는 방법은 테스트 클래스쪽에 >> 누르기
@DisplayName("") : 테스트의 내용을 한눈에 알아볼 수 있게 네이밍 해줄 때
= 메서드 이름을 신경 쓸 필요 없음
@Nested : "주제 별로 테스트를 그룹지어서 파악하기 좋습니다."
@Order : 메서드 단위로 순서를 매길 때
@RepeatedTest : 메서드를 반복해서 테스트 = for문
value : 5번 / name : 네이밍
@ParameterizedTest : "파라미터 값 활용하여 테스트 하기"
전달되는 파라미터 수 만큼 실행이 됨 = 리스트같이
Calculator calculator;
@BeforeEach
void setUp() {
calculator = new Calculator();
}
@BeforeEach를 통해서
각 테스트가 실행되기 전 위에있는 new Calculator를 수행함으로써
매번 새로운 객체가 생성된다
Given-When-Then 패턴
Mockito
myselectshop productService쪽 수정
원래 Service를 실행하려면 Repository나 다른것들이 필요한데 그것들이 없기 때문에 오류가 나고 있다.(findById 등)
@ExtendWith(MockitoExtension.class) // @Mock 사용을 위해 설정합니다.
수행하면 오류가 난다.
test2()는 "관심 상품 희망가 - 최저가 이상으로 변경" 인데,
if() 부분인 최소값 체크하는 기능만 테스트 하기를 원하기 때문에
given을 쓸 필요가 없다.
통합 테스트(@SpringBootTest)
단위 테스트 VS 통합 테스트
단위 테스트 시 Spring은 동작 x
@SpringBootTest
테스트들은 테스트 동작 환경이 따로 있으며, 서로 영향이 받지 않도록 다 따로 동작한다
= 필드들이 공유가 되지 않는다.
= 필드가 공유된다.
Controller 테스트
Security를 같이 사용하고 있기 때문에 까다롭다
@WebMvcTest : Controller 테스트
MockMvc, 가짜 Principal도 선언 해준다.
.andExpect(view().name("login")) : 반환되는 페이지 Html이 login인지
Test는 Repository, 즉 jpa가 필요 없는데 @EnableJpaAuditing가 방해하기 때문에 오류가 남
-> JpaConfig를 만들어서 해결 / @EnableJpaAuditing와 @Configuration 달아준다
Controller는 Security도 엮여있고 다른것들도 많이 엮여있어서 가짜로 만들어줘야할 것도 많다
-> 나중에 왜 방해하는지, 안에 메서드들이 어떤건지 알아보기
이정도만 이해하고 넘어가기
test2 - is3xxRedirection : redirect할때 3으로 시작하기 때문에(filter에서)
test3 - writeValueAsString : 클래스(ProductRequestDto)를 String타입으로 바꾸는것
클라이언트는 Json 서버는 String이 필요하기 때문에
Service 테스트 : Mockito
Controller 테스트 : WebMvcTest
모듈연결 테스트 : SpringBootTest
@MockBean : UserController에는 UserService / FolderService / KakaoService 를 Bean 주입받아야 하는데
이 어노테이션을 사용해서 가짜 Bean을 생성해 준다는 의미
MySelectShop Top5 회원
목적 : 수행시간 측정하는 코드
프로젝트 컨트롤러에 들어갔다가 나온 시간 측정
ApiUseTime이라는 Entity 생성해서 관리
부가 기능 모듈화
Spring AOP
어드바이스 : "언제" 수행할 건지 정하는 것
포인트컷 : 위치. "어디에" 수행할건지
@Aspect
어드바이스 종류
@AfterThrouwing : 호출 실패 시(예외가 발생한 경우)
포인트컷 사용법
execution(public com.sprata.myselectshop.controller..(..))
?는 생략 가능하다는 뜻
com.springex.service
com.springex.service 패키지만 선택
com.springex.service..
com.springex.service 패키지와 하위 패키지까지 모두 선택
com.springex.service..member
com.springex.service 패키지의 하위 패키지 중 member인 패키지
훨씬 더 많지만 검색해서 찾아보자
다 따라다니면서 넣어줄 순 없으니 @Pointcut을 통해 포인트컷 재사용 가능
예시)
@Pointcut("execution(* com.sparta.myselectshop.controller.ProductController.*(..))")
private void product() {}
@Around("product() || folder() || naver()")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
...
}
// 핵심기능 수행
Object output = joinPoint.proceed();
API 예외처리
에러코드 200 번대 : 성공
에러코드 400 번대 : 클라이언트 에러
에러코드 500 번대 : 서버 에러
에러의 정확 응답을 위해 ResponseEntity 클래스 사용해서 에러메시지 반환 <- 오늘뭐먹지 프로젝트때 비슷하게 해본것!
@ExceptionHandler({IllegalArgumentException.class})
public ResponseEntity<RestApiException> handleException(IllegalArgumentException ex) {
RestApiException restApiException = new RestApiException(ex.getMessage(), HttpStatus.BAD_REQUEST.value());
return new ResponseEntity<>(
// HTTP body
restApiException,
// HTTP status code
HttpStatus.BAD_REQUEST
);
}
Spring의 Global 에러 처리
매 Controller마다 해주는게 아니라 전체적으로 달아주기
@RestControllerAdvice : 모든 Controller에서 발생하는 에러를 다 가져올 수 있음.
"클래스" 단위의 어노테이션이다.
Error 메시지 관리하는 법
Spring의 properties 파일을 통해 관리 할 수 있다.
사용하는 클래스에서 MessageSource 클래스 사용해서 가져옴
Exception클래스는 번개표시로 바뀐다
@ExceptionHandler({ProductNotFoundException.class})
public ResponseEntity<RestApiException> notFoundProductExceptionHandler(ProductNotFoundException ex) {
RestApiException restApiException = new RestApiException(ex.getMessage(), HttpStatus.NOT_FOUND.value());
return new ResponseEntity<>(
// HTTP body
restApiException,
// HTTP status code
HttpStatus.NOT_FOUND
);
}