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
댓글
아직 댓글이 없습니다