Validation(유효성 검사)
: 서비스의 비즈니스 로직이 올바르게 동작하기 위해 사용되는 데이터에 대한 사전 검증
즉, Input 데이터에 대해 의도한 형식의 값이 제대로 들어오는지 체크하는 과정
(역시 이렇게만 봐도 Gradle이 편하다)
// Gradle
implementation 'org.springframework.boot:spring-boot-starter-validation'
// Maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
○ 일반적인 유효성검사
● 간단한 검증을 하더라도 검증관련 로직이 길어지는 경향이 有
● 검증 로직이 중복으로 여러 Layer(Controller, Service, DAO 등)에 존재
● Layer에 검증 로직이 섞여있기 때문에 추측이 어렵고 Application이 복잡해짐
if (itemDTO == null) {
throw new IllegalArgumentException("아이템 정보가 존재하지 않습니다.");
}
if (itemDTO.getItemName() == null || itemDTO.getItemName().isEmpty()) {
throw new IllegalArgumentException("아이템 명은 필수 입니다.");
}
if (itemDTO.getQuantity() == null) {
throw new IllegalArgumentException("아이템 개수는 필수 입니다.");
}
...
○ @Valid를 이용한 유효성 검사
public ResponseEntity<String> save(@Valid @RequestBody ItemDTO itemDTO) {
itemService.save(itemDTO);
return new ResponseEntity<>(HttpStatus.CREATED);
}
Validation 관련 주요 어노테이션
○ 문자열
● @Size : 문자의 길이 조건
● @NotNull : null 값 불가
● @NotEmpty : @NotNull + "" 값 불가
● @NotBlank : @NotEmpty + " " 값 불가
● @Pattern(regexp="", message="") : 정규식을 통한 조건
● @Email : 이메일
○ 날짜
● @Past : 과거 날짜
● @PastOrPresent : @Past + 오늘 날짜
● @Future : 미래 날짜
● @FutureOrPresent : @Future + 오늘 날짜
○ 숫자
● @Max / @Min : 최대/최소값
● @Positive / @PositiveOrZero : 양수와 0만
● @Negative / @NegativeOrZero : 음수와 0만
○ 직접 검증
● AssertTrue : 참
● AssertFalse : 거짓
public class ProductDto {
private String productId;
@NotBlank
@Size(min = 4, max = 10)
private String ProductName;
@Min(value = 50)
private int ProductPrice;
@Min(value = 0)
@Max(value = 99)
private int productStock;
}
Exception
○ 예외 클래스
● 모든 예외 클래스는 Throwable 클래스를 상속 받고 있음
● RuntimeException은 Unchecked Exception이며, 그 외 Exception은 Checked Exception
Checked Exception | Unchecked Exception | |
처리 여부 | 반드시 예외 처리 | 명시적 처리 강제 X |
확인 시점 | 컴파일 단계 | 실행 중 단계 |
예외 발생 시 트랜잭션 | 롤백하지 X | 롤백 |
대표 예외 | IOException SQLException |
NullPointerException Illegal ArgumentException IndexOutOfBoundException SystemException |
○ SpringBoot의 예외 처리 방식
● @ControllerAdvice를 통한 모든 Controller에서 발생할 수 있는 예외 처리
● @exceptionHandler를 통한 특정 Controller의 예외처리
@ControllerAdvice로 모든 컨트롤러에서 발생할 예외 정의
→ @ExceptionHandler를 통해 발생하는 예외마다 처리할 메소드 정의
○ @ExceptionHandler
● Controller 계층에서 발생하는 에러를 잡아 메서드로 처리해주는 기능
● Service, Repository에서 발생하는 에러는 제외
@Controller
public class SimpleController {
// ...
@ExceptionHandler
public ResponseEntity<String> handle(IOException ex) {
// ...
}
}
● 여러 개의 Exception 처리
- @ExceptionHandler의 value 값으로 어떤 Exception을 처리할 것인지 넘겨 줄 수 있음
- value를 설정하지 않으면 모든 Exception을 잡게되어 구체적으로 명시해주는 것이 좋음
@Controller
public class SimpleController {
// ...
@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(Exception ex) {
// ...
}
}
● 우선 순위 : Exception.class 보다 구체적 오류클래스(NullPointerException.class)가 우선순위 높음
○ @ControllerAdvice
● @ControllerAdvice 안에서 @ExceptionHandler를 사용하여 에러를 잡을 수 있음
@ControllerAdvice
public class ExceptionHandlers {
@ExceptionHandler(FileNotFoundException.class)
public ResponseEntity handleFileException() {
return new ResponseEntity(HttpStatus.BAD_REQUEST);
}
}
● 범위 설정
- @ControllerAdvice는 모든 에러를 잡아주어 일부 에러만 처리하고 싶을 경우 따로 설정해주면 됨
// 1.
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}
// 2.
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}
// 3.
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}
○ @RestControllerAdvice
● @RestControllerAdvice는 @ControllerAdvice와 @ResponseBody로 이루어져 있어 오류 메세지를
Response body(= json)으로 리턴 가능
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {
// ...
}
Custom Exception
: 비지니스 로직에 의한 논리 오류인 경우 사용자 정의 예외를 이용하는 것이 좋을 수 있음
@Getter
public class HubException extends Exception {
private HttpStatus httpStatus;
public HubException(HttpStatus httpStatus, String message) {
super(message);
this.httpStatus = httpStatus;
}
}
○ Custom Exception 장점
● 예외 이름으로도 정보 전달 가능 : 상품 오류, 고객 오류 등
● 상세한 예외 정의 가능 : 재고상품 50 미만 오류, 고객 잔고 10만원 미만 오류 등
● 예외에 대한 응집도 향상 : 예외 메시지, 데이터 가공 메소드 등 각 예외에 맞는 메시지/메소드 관리 용이
● 예외 발생 후 처리가 용이 : 각 예외별 후 처리용 메소드 정의 가능
○ Enum
: 일정 개수의 상수 값을 정의하고 그 외의 값은 허용하지 않음
(과거의 경우 : 특정 상수 값을 사용하기 위해선 모두 상수로 선언해 사용했음)
● 정의 및 생성
public enum Day1 {
MON, TUE, WED, THU, FRI, SAT, SUN
};
Day1.MON.name(); // MON
public enum Day2 {
MON("Monday"), TUE("Tuesday"), WED("Wednesday");
private final String label;
Day2(String label) {
this.label = label;
}
public String label() {
return label;
}
};
Day2.MON.name(); // MON
Day2.MON.label(); // Monday
● Enum 값 찾기 : name을 이용해 Enum 클래스 정의
Day2 day = Day2.valueOf("TUE");
● values() : enum 타입의 모든 값들을 배열로 만들어 리턴해주는 메소드
for (Day2 day : Day2.values()) {
day.label();
}
TDD(Test Drive Development)
: 테스트 주도 개발이라는 의미(테스트를 먼저 설계 및 구축 후 테스트를 통과할 수 있는 코드를 작성하는 것)
코드 작성 후 테스트를 진행하는 지금까지 사용된 일반적인 방식과 다소 차이가 있음
○ 애자일 개발 방식 중 하나
● 단계적 목표에 대해 설정해 진행하고자 하는 것에 대한 결정 방향의 갭을 줄이고자 함
● 최초 목표에 맞춘 테스트를 구축해 그에 맞게 코드를 설계하기 때문에 보다 적은 의견 충돌 기대 가능
○ 테스트 코드 작성 목적
● 코드의 안정성 높일 수 있음
● 기능을 추가하거나 변경하는 과정에서 발생할 수 있는 Side-Effect를 줄일 수 있음
● 해당 코드가 작성된 목적을 명확하게 표현할 수 있음
- 코드에 불필요한 내용이 들어가는 것을 비교적 줄일 수 있음
JUnit
: Java 진영의 대표적인 Test Framework, Annotation을 기반으로 테스트를 지원
단정문(Assert)로 테스트 케이스의 기대값에 대해 수행 결과 확인
단위 테스트를 위한 도구를 제공
- 코드의 특정 모듈이 의도된 대로 동작하는지 테스트
- 모든 함수(메소드)에 대한 테스트 케이스를 작성
○ JUnit LifeCycle Annotation
Annotation | Description |
@Test | 테스트용 메소드를 표현하는 어노테이션 |
@BeforeEach | 각 테스트 메소드가 시작되기 전에 실행되어야 하는 메소드 표현 |
@AfterEach | 각 테스트 메소드가 시작된 후 실행되어야 하는 메소드 표현 |
@BeforeAll | 테스트 시작 전에 실행되어야 하는 메소드 표현(static 처리 필요) |
@AfterAll | 테스트 종류 후에 실행되어야 하는 메소드 표현(static 처리 필요) |
○ JUnit Main Annotation
● @SpringBootTest
- 통합 테스트 용도로 사용
- @SpringBootApplication을 찾아가 하위 모든 Bean을 스캔해 로드
- 그 후 Test용 Application Context를 만들어 Bean을 추가하고, MockBean을 찾아 교체
● @ExtendWith
- @ExtendWith는 메인으로 실행될 Class를 지정 가능
- @SpringBootTest : 기본적으로 @ExtendWith가 추가되어 있음
● @WebMvcTest(Class명.class)
- ()에 작성된 클래스만 실제로 로드해 테스트 진행
- 매개변수 지정하지 않으면 컨트롤러와 연관된 Bean 모두 로드
( @Controller, @RestController, @RestControllerAdvice 등 )
- @SpringBootTest 대신 컨트롤러 관련 코드만 테스트할 경우 사용
● @Autowired about Mockbean
- Controller의 API를 테스트하는 용도인 MockMvc객체를 주입 받음
- perform() 메소드를 활용해 컨트롤러의 동작 확인 가능
- andExpect(), andDo(), andReturn() 등의 메소드를 같이 활용
● @MockBean
- 테스트할 클래스에서 주입 받고 있는 객체에 대해 가짜 객체를 생성해주는 어노테이션
- 실제 행위는 하지 않음
- given() 메소드를 활용해 가짜 객체의 동작에 대해 정의해 사용 가능
● @AutoConfigureMockMvc
- spring.test.mockmvc의 설정을 로드하면서 MockMvc의 의존성을 자동 주입
- MockMvc 클래스 : RestAPI 테스트를 할 수 있는 클래스
● @Import
- 필요한 Class들을 Configuration으로 만들어 사용 가능
- Configuration Component 클래스도 의존성 설정 가능
- Import된 클래스는 주입으로 사용 가능
통합 테스트
: 여러 기능을 조합해 전체 비지니스 로직이 제대로 동작하는 지 확인하는 것
@SpringBootTest를 사용해 진행
● 대규모 프로젝트에서 사용할 경우, 테스트를 실행할 때마다 모든 빈을 스캔하고 로드하는 작업이 반복
So, 매번 무거운 작업을 수행해야함
단위 테스트
: 프로젝트에 필요한 모든 기능에 대한 테스트를 각각 진행하는 것
일반적으로 SpringBoot에서는 'spring-boot-starter-test' 디펜던시만으로 의존성을 모두 가질 수 있음
○ F.I.R.S.T 원칙
● Fast : 테스트 코드의 실행은 빠르게 진행되어야 함
● Independent : 독립적인 테스트가 가능해야 함
● Repeatable : 테스트는 매번 같은 결과를 만들어야 함
● Self-Validating : 테스트는 그 자체로 실행해 결과를 확인 가능해야함
● Timely : 단위 테스트는 비지니스 코드가 완성되기 전에 테스트가 가능해야함
- TDD의 원칙 : 코드가 완성되기 전부터 테스트가 따라와야 한다는 원칙
'Networks > SpringBoot' 카테고리의 다른 글
SK networks AI Camp - SpringBoot(3) (0) | 2024.09.09 |
---|---|
SK networks AI Camp - SpringBoot (5) | 2024.09.06 |