포스트

2022-03-24-TIL

Today I Learned

JIB

Jib은 Google이 만든 도구로, Dockerfile이나 Docker 데몬 없이 Maven/Gradle 플러그인만으로 자바 애플리케이션의 Docker/OCI 이미지를 만들어 레지스트리에 푸시한다. 애플리케이션을 의존성, 리소스, 클래스 파일 레이어로 나누어 담기 때문에 코드만 바뀌면 클래스 레이어만 다시 만들고 푸시해서 빌드가 빠르다.

  • https://blog.naver.com/PostView.nhn?blogId=adamdoha&logNo=221865323086
  • https://nesoy.github.io/articles/2020-11/Jib

통합테스트

통합 테스트는 단위 테스트를 통과한 모듈들을 결합해서 모듈 사이의 인터페이스와 상호 작용을 검증하는 단계다. 결합 순서에 따라 방식이 나뉜다.

  • 빅뱅: 모든 모듈을 한 번에 결합해 테스트한다. 준비는 간단하지만 실패 원인을 찾기 어렵다.
  • 하향식(top-down): 상위 모듈부터 결합하고, 아직 없는 하위 모듈은 스텁으로 대체한다.
  • 상향식(bottom-up): 하위 모듈부터 결합하고, 상위 호출자는 드라이버로 대체한다.
  • 샌드위치(sandwich): 중간 계층을 기준으로 상위는 하향식, 하위는 상향식으로 동시에 진행하는 혼합 방식이다.

  • https://miinsun.tistory.com/137
  • https://needjarvis.tistory.com/443
  • https://www.geeksforgeeks.org/sandwich-testing-software-testing/

@MockBean, @SpyBean

둘 다 스프링 컨텍스트의 빈을 테스트용 객체로 교체한다. @MockBean은 모든 메서드가 기본값(null, 0, 빈 컬렉션)을 반환하는 mock으로 바꾸므로 필요한 동작을 모두 stub해야 한다. @SpyBean은 실제 빈을 감싸서 stub하지 않은 메서드는 실제 구현을 호출하고, 일부 메서드만 원하는 동작으로 바꾼다. 외부 API 호출 하나만 가짜로 바꾸고 나머지는 실제로 동작시키고 싶을 때 @SpyBean이 유용하다. 두 애너테이션 모두 컨텍스트 구성을 바꾸므로 테스트 클래스마다 다르게 쓰면 컨텍스트 캐시를 재사용하지 못한다.

  • https://jojoldu.tistory.com/320

Controller Test

컨트롤러 테스트는 보통 @WebMvcTest(MemberController.class)로 웹 계층만 띄우고, 서비스는 @MockBean으로 대체한 뒤 MockMvc로 요청을 보내 상태 코드와 응답 바디를 검증한다. 서비스 로직은 서비스 테스트에서 따로 검증하므로, 컨트롤러 테스트는 요청 매핑, 입력 검증, 응답 형식에 집중한다.

  • https://royleej9.tistory.com/entry/Springboot-junit-controller-%ED%85%8C%EC%8A%A4%ED%8A%B8%ED%95%98%EA%B8%B0

Annotation

애너테이션은 코드에 붙이는 메타데이터다. 그 자체로는 동작이 없고, 누군가가 읽어서 처리해야 의미가 생긴다. 처리 시점은 @Retention으로 정한다.

Retention남는 곳처리하는 쪽
SOURCE소스 코드에만컴파일러, 애너테이션 프로세서
CLASS (기본값)클래스 파일까지바이트코드 도구
RUNTIME실행 중에도리플렉션으로 읽는 프레임워크

Spring의 @Component나 JPA의 @Entity는 RUNTIME 애너테이션이라 실행 중에 리플렉션으로 읽힌다. Lombok, MapStruct, QueryDSL은 컴파일 시점에 동작하는 애너테이션 프로세서(JSR 269의 javax.annotation.processing)로 코드를 생성하거나 바꾼다. 링크된 글들은 사용자 정의 애너테이션을 선언하고 프로세서를 작성하는 방법까지 다룬다.

  • https://blog.gangnamunni.com/post/kotlin-annotation-codegeneration/
  • https://www.javatpoint.com/how-annotation-works-in-java
  • https://www.geeksforgeeks.org/annotations-in-java/
  • https://dzone.com/articles/how-annotations-work-java
  • http://tutorials.jenkov.com/java/annotations.html
  • http://travisdazell.blogspot.com/2012/10/writing-annotation-based-processor-in.html
  • https://xperti.io/blogs/what-are-annotations-in-java/

@Override

@Override는 @Retention(SOURCE)로 선언되어 있어 클래스 파일에는 남지 않는다. 컴파일러가 컴파일 중에 이 애너테이션이 붙은 메서드가 실제로 상위 타입의 메서드를 재정의하는지 검사하고, 아니면 컴파일 오류를 낸다. 메서드 이름 오타나 시그니처 불일치로 재정의가 아닌 새 메서드를 만드는 실수를 막아 준다.

  • https://stackoverflow.com/questions/18189980/how-do-annotations-like-override-work-internally-in-java

JSON array parsing in MockMvc

MockMvc의 jsonPath는 JsonPath 표현식으로 응답 JSON의 특정 위치를 검증한다. 배열은 인덱스로 접근하고, 배열 안의 배열도 $[0][1]처럼 이어서 쓴다.

1
2
3
4
mockMvc.perform(get("/members"))
        .andExpect(jsonPath("$", hasSize(2)))
        .andExpect(jsonPath("$[0].name").value("kim"))
        .andExpect(jsonPath("$[*].name", containsInAnyOrder("kim", "lee")));
  • https://stackoverflow.com/questions/25614593/asserting-array-of-arrays-with-jsonpath-and-spring-mvc

@RequestBody in MockMvc

@RequestBody를 받는 API를 테스트할 때는 요청 객체를 ObjectMapper로 JSON 문자열로 바꿔 content()에 넣고, contentType(MediaType.APPLICATION_JSON)을 지정해야 한다. Content-Type을 빠뜨리면 메시지 컨버터를 찾지 못해 415 Unsupported Media Type이 반환된다.

1
2
3
4
mockMvc.perform(post("/members")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(request)))
        .andExpect(status().isCreated());
  • https://stackoverflow.com/questions/20504399/testing-springs-requestbody-using-spring-mockmvc

MockMvc

MockMvc는 실제 서버를 띄우지 않고 DispatcherServlet에 가짜 요청을 보내 스프링 MVC의 요청 처리 흐름(필터, 컨트롤러, 메시지 변환, 예외 처리)을 테스트하게 해 준다. perform()으로 요청을 보내고, andExpect()로 상태 코드, 헤더, 바디를 검증하며, andDo(print())로 요청과 응답 내용을 출력해 디버깅한다.

  • https://shinsunyoung.tistory.com/52

Errors

should be mapped with insert=”false” update=”false”

Hibernate가 같은 컬럼이 엔티티 안에서 두 번 매핑되었을 때 내는 오류다. 예를 들어 @Column(name = "team_id") 필드와 @JoinColumn(name = "team_id") 연관관계가 같은 컬럼을 가리키면, INSERT나 UPDATE 때 어느 쪽 값을 써야 할지 정할 수 없다. 둘 중 하나를 insertable = false, updatable = false로 지정해 읽기 전용으로 만들면 해결된다.

  • https://solbel.tistory.com/1480

You aren’t using a compiler supported by lombok

IntelliJ의 빌드 프로세스와 Lombok 버전이 맞지 않을 때 나는 오류다. 링크된 글이 정리한 해결 방법은 Lombok을 1.18.16 이상으로 올리고, IntelliJ의 annotation processing을 켜는 것이다. 그래도 안 되면 아래 항목의 VM 옵션으로 JPS의 증분 애너테이션 처리를 끈다.

  • https://ifuwanna.tistory.com/292

JPS incremental annotation processing is disabled.

IntelliJ의 빌드 시스템(JPS)은 증분 컴파일을 위해 애너테이션 프로세서가 생성하는 파일의 의존 관계를 추적하는데, Lombok처럼 컴파일러 내부에 접근하는 프로세서와 충돌하면 이 경고가 나며 부분 재컴파일 결과가 부정확할 수 있다. Stack Overflow 답변에 따르면 Settings > Build, Execution, Deployment > Compiler의 Shared build process VM options에 -Djps.track.ap.dependencies=false를 넣어 의존성 추적을 끌 수 있고, Lombok 버전을 올려 해결된 경우도 있다.

  • https://stackoverflow.com/questions/66170975/jps-incremental-annotation-processing-is-disabled-compilation-results-on-partia
  • https://www.inflearn.com/questions/195661

class file for javax.interceptor.InterceptorBinding not found

JDK 8의 javadoc 도구가 Maven javadoc 플러그인으로 문서를 생성할 때 나는 오류다. Stack Overflow 답변에 따르면 javax.transaction.Transactional 애너테이션 자체에 @InterceptorBinding이 붙어 있는데, 이 애너테이션을 담은 javax.interceptor-api는 보통 전이 의존성으로 받아지지 않아 클래스패스에 없다. javax.interceptor-api 의존성을 추가하거나, Spring 애플리케이션이라면 org.springframework.transaction.annotation.Transactional을 import하도록 바꾸면 해결된다.

  • https://stackoverflow.com/questions/27808734/jdk8-error-class-file-for-javax-interceptor-interceptorbinding-not-found-whe
참고한 자료외부 출처 2

외부 출처

이 글은 저작권자의 CC BY 4.0 라이선스를 따릅니다.

댓글

아직 댓글이 없습니다