본문 바로가기
TIL/Spring

23.07.12

by J1-H00N 2023. 7. 12.

통합 테스트

단위 테스트는 각 모듈이나 클래스에 대한 세심한 테스트가 가능하지만 모듈간의 상호 작용 검증은 할 수 없다. 이를 위해 두 개 이상의 모듈이 연결된 상태로 테스트하는 것이 통합 테스트다. 모듈 간의 연결에서 발생하는 에러를 검증할 수 있다.

단위 테스트 시 spring은 작동하지 않는데, @SpringBootTest 어노테이션을 통해 테스트 시 스프링을 작동시킬 수 있다. 즉,

Spring IoC/DI 기능, Repository를 사용해 DB CRUD가 가능

 

유저별 api 요청 시간을 조회하는 기능을 추가하기 위해, Scratch 파일을 통해 알아보려고 한다. 

intellij > File > Scratch > Java 를 통해 만들고 아래와 같이 만든다. 생성하면 파일 리스트 최하단에 위치한다.

class Scratch {
    public static void main(String[] args) {
        // 측정 시작 시간
        long startTime = System.currentTimeMillis();

        // 함수 수행
        long output = sumFromOneTo(1_000_000_000);

        // 측정 종료 시간
        long endTime = System.currentTimeMillis();

        long runTime = endTime - startTime;
        System.out.println("소요시간: " + runTime);
    }

    private static long sumFromOneTo(long input) {
        long output = 0;

        for (int i = 1; i < input; ++i) {
            output = output + i;
        }

        return output;
    }
}

위 코드를 실행시키면 runTime을 출력해서 보여준다.(지금은 450 언저리)

 

위와 같이 로그인, 회원가입 등의 핵심기능 외에 핵심기능을 보조하는 부가기능(사용시간 조회)들은 따로 모듈화를 하는 것이 좋다.

그 이유는 따로 모듈화 하지 않을 경우 핵심기능이 많을 경우 부가기능 코드를 각 핵심기능에 추가해줘야 하고, 후에 핵심기능이 추가되는 경우 부가기능을 추가해야 하며, 부가기능에 변경점이 생기면 다시 모든 핵심기능에서 부가기능을 수정해줘야 하는 번거로운 작업을 해줘야 한다.

AOP를 통해 핵심기능과 분리해서 부가기능을 중심으로 모듈화 해서 설계, 구현이 가능하다.

 

AOP는 핵심기능과 부가기능을 따로 모듈화 해서 합쳐주는 것이다.

Spring AOP Annotation

  1. @Aspect
    • Spring 빈(Bean) 클래스에만 적용 가능합니다.
  2. 어드바이스(부가 기능을 언제 실행할지 정함) 종류
    • @Around: '핵심기능' 수행 전과 후 (@Before + @After)
    • @Before: '핵심기능' 호출 전 (ex. Client 의 입력값 Validation 수행)
    • @After: '핵심기능' 수행 성공/실패 여부와 상관없이 언제나 동작 (try, catch 의 finally() 처럼 동작)
    • @AfterReturning: '핵심기능' 호출 성공 시 (함수의 Return 값 사용 가능)
    • @AfterThrowing: '핵심기능' 호출 실패 시. 즉, 예외 (Exception) 가 발생한 경우만 동작 (ex. 예외가 발생했을 때 개발자에게 email 이나 SMS 보냄)
  3. 포인트컷(부가기능을 어디에서 적용할지 정함)
    • 포인트컷 Expression Language
      • modifiers-pattern
        • public, private, *
      • return-type-pattern
        • void, String, List<String>, *****
      • declaring-type-pattern
        • 클래스명 (패키지명 필요)
        • com.sparta.myselectshop.controller.* - controller 패키지의 모든 클래스에 적용
        • com.sparta.myselectshop.controller.. - controller 패키지 및 하위 패키지의 모든 클래스에 적용
      • method-name-pattern(param-pattern)
        • 함수명
          • addFolders : addFolders() 함수에만 적용
          • add* : add 로 시작하는 모든 함수에 적용
        • 파라미터 패턴 (param-pattern)
          • (com.sparta.myselectshop.dto.FolderRequestDto) - FolderRequestDto 인수 (arguments) 만 적용
          • () - 인수 없음
          • (*) - 인수 1개 (타입 상관없음)
          • (..) - 인수 0~N개 (타입 상관없음)
      • @Pointcut
        • 포인트컷 재사용 가능
        • 포인트컷 결합 (combine) 가능
더보기

포인트컷 Expression 형태

execution(modifiers-pattern? return-type-pattern declaring-type-pattern? method-name-pattern(param-pattern) throws-pattern?)

? 는 생략 가능

포인트컷 Expression 예제

@Around("execution(public * com.sparta.myselectshop.controller..*(..))")
public Object execute(ProceedingJoinPoint joinPoint) throws Throwable { ... }

 

포인트컷 결합

@Component
@Aspect
public class Aspect {
	@Pointcut("execution(* com.sparta.myselectshop.controller.*.*(..))")
	private void forAllController() {}

	@Pointcut("execution(String com.sparta.myselectshop.controller.*.*())")
	private void forAllViewController() {}

	@Around("forAllContorller() && !forAllViewController()")
	public void saveRestApiLog() {
		...
	}

	@Around("forAllContorller()")
	public void saveAllApiLog() {
		...
	}	
}

 

spring AOP 동작 이해

 

API 예외처리

에러를 처리하는 것 역시 관심사를 분리해서 더 효율적으로 처리 할 수 있지 않을까 고민해보는 시간이 필요하기 때문에 예외처리를 따로 배운다.

spring에서는 아래와 같은 enum도 제공해준다.

org.springframework.http > HttpStatus

public enum HttpStatus {
	// 1xx Informational
	CONTINUE(100, Series.INFORMATIONAL, "Continue"),
	// ...

	// 2xx Success
	OK(200, Series.SUCCESSFUL, "OK"),
	CREATED(201, Series.SUCCESSFUL, "Created"),
	// ...

	// 3xx Redirection
	MULTIPLE_CHOICES(300, Series.REDIRECTION, "Multiple Choices"),
	MOVED_PERMANENTLY(301, Series.REDIRECTION, "Moved Permanently"),
	FOUND(302, Series.REDIRECTION, "Found"),
	// ...

	// --- 4xx Client Error ---
	BAD_REQUEST(400, Series.CLIENT_ERROR, "Bad Request"),
	UNAUTHORIZED(401, Series.CLIENT_ERROR, "Unauthorized"),
	PAYMENT_REQUIRED(402, Series.CLIENT_ERROR, "Payment Required"),
	FORBIDDEN(403, Series.CLIENT_ERROR, "Forbidden"),
	// ...
	

	// --- 5xx Server Error ---
	INTERNAL_SERVER_ERROR(500, Series.SERVER_ERROR, "Internal Server Error"),
	NOT_IMPLEMENTED(501, Series.SERVER_ERROR, "Not Implemented"),
	BAD_GATEWAY(502, Series.SERVER_ERROR, "Bad Gateway"),
	// ...

 

Controller마다 ExceptionHandler를 달아주기에는 각각에 에러 종류마다 다르게 걸어줘야하고, 각 컨트롤러마다 일일히 달아주기에는 비효율적이므로, 일괄적으로 global하게 예외처리를 해줄 수 있다.

@ControllerAdvice, @RestControllerAdvice 사용

'TIL > Spring' 카테고리의 다른 글

23.07.26  (0) 2023.07.26
23.07.25  (0) 2023.07.25
23.07.11  (0) 2023.07.11
23.07.10  (0) 2023.07.10
23.06.21  (0) 2023.06.21