2022-03-17-TIL
Today I Learned
Database Case-Insensitive
Oracle은 기본적으로 문자열을 이진 비교하므로 대소문자를 구분한다. 링크된 Oracle 블로그 글과 Oracle 문서가 설명하는 방법은 다음과 같다.
UPPER(name) = UPPER(:keyword)처럼 함수로 맞춘다. 인덱스를 쓰려면UPPER(name)함수 기반 인덱스가 필요하다.- 세션에서
NLS_COMP=LINGUISTIC,NLS_SORT=BINARY_CI를 설정하면 비교 연산이 대소문자를 무시한다._AI접미사(BINARY_AI)는 악센트까지 무시한다. - 12c Release 2부터는
COLLATE BINARY_CI를 표현식이나 컬럼 정의에 직접 지정할 수 있다.
언어 기반 비교는 이진 비교보다 느리므로 NLSSORT를 이용한 언어 인덱스를 함께 고려해야 한다. MySQL은 반대로 기본 콜레이션(utf8mb4_0900_ai_ci 등의 _ci)이 대소문자를 구분하지 않는다는 점도 DB를 옮길 때 차이가 된다.
- https://blogs.oracle.com/sql/post/how-to-do-case-insensitive-and-accent-insensitive-search-in-oracle-database
Spring Boot Test with JUnit
Spring Boot는 spring-boot-starter-test에 JUnit 5, AssertJ, Mockito, Spring Test를 함께 묶어 제공한다. 테스트는 @SpringBootTest로 전체 컨텍스트를 띄우는 통합 테스트와, 필요한 계층만 띄우는 슬라이스 테스트로 나눌 수 있다. 전체 컨텍스트를 띄우는 테스트는 느리므로 대부분의 로직은 스프링 없이 단위 테스트로 검증하고, 계층 간 연결만 통합 테스트로 확인하는 편이 빠르다.
- https://meetup.toast.com/posts/124
Java 17 and Spring 6
JDK 11은 상용 지원 기간이 JDK 8보다 짧으며 2023년 말에 단계적으로 중단될 예정이다. 차세대 LTS인 JDK 17은 최소 2026년까지 지원할 예정이다. 또한 Spring Framework 6.x 버전 내에서 LTS(2027년)까지 JDK 릴리즈가 지속적으로 지원된다.
링크된 Spring 블로그 글의 핵심은 Spring Framework 6가 Java 17과 Jakarta EE 9를 최소 요구 사항으로 삼고, 그 위의 Spring Boot 3도 런타임에 JDK 17 이상을 요구한다는 것이다. Jakarta EE 9부터 패키지 이름이 javax.*에서 jakarta.*로 바뀌었기 때문에, Spring 6로 올라가려면 javax.persistence, javax.servlet 같은 import를 모두 바꿔야 한다. 지금부터 Java 17로 옮겨 두면 이 전환이 한결 수월하다.
- https://spring.io/blog/2021/09/02/a-java-17-and-jakarta-ee-9-baseline-for-spring-framework-6
코드리뷰 문화를 정착시키기 위한 방법
코드 리뷰가 정착하려면 리뷰가 부담스러운 일이 아니라 일상적인 절차가 되어야 한다. 흔히 권하는 방법은 PR을 작게 나누어 리뷰 시간을 줄이는 것, 포맷이나 린트처럼 기계가 잡을 수 있는 것은 자동화해서 사람은 설계와 로직에 집중하는 것, 코멘트가 요청인지 제안인지 질문인지 구분해서 남기는 것, 리뷰 응답 시간에 대한 팀의 약속을 정하는 것이다. 코드에 대한 의견을 사람에 대한 평가로 받아들이지 않는 분위기도 함께 만들어야 한다.
- https://brunch.co.kr/@cleancode/43
테스트 코드
Unit Test with Mockito
서비스 계층을 단위 테스트할 때는 리포지토리를 Mockito mock으로 바꿔 DB 없이 로직만 검증한다. 링크들이 다루는 대표적인 상황은 다음과 같다.
save()결과를 흉내 내기:given(repository.save(any())).willReturn(saved)처럼 반환값을 정한다. 넘긴 객체를 그대로 돌려주려면willAnswer(inv -> inv.getArgument(0))을 쓴다.- void 메서드가 예외를 던지게 하기:
when(...).thenThrow(...)는 반환값이 있어야 쓸 수 있으므로doThrow(ex).when(mock).delete(any())형식을 쓴다. - 호출 여부 검증:
verify(repository).save(any()),verify(repository, never()).delete(any()).
@Mock은 순수 Mockito mock이고, @MockBean은 스프링 컨텍스트 안의 빈을 mock으로 바꾼다. 스프링 없이 돌리는 단위 테스트에는 @ExtendWith(MockitoExtension.class)와 @Mock, @InjectMocks면 충분하고, @MockBean은 컨텍스트를 띄우는 테스트에서만 쓴다.
- https://stackoverflow.com/questions/51247796/how-to-mock-jpa-repositorys-save-method-in-unit-tests
- https://juneyr.dev/2019-02-08/mockito-repo-save
- https://mkyong.com/spring-boot/spring-mockito-unable-to-mock-save-method/
- https://stackoverflow.com/questions/43880816/mockito-and-java-spring-repository-in-tests
- https://cornswrold.tistory.com/369
- https://stackoverflow.com/questions/15156857/mockito-test-a-void-method-throws-an-exception
- https://cobbybb.tistory.com/16
- https://www.baeldung.com/java-spring-mockito-mock-mockbean
- https://blog.naver.com/whdgml1996/221826586051
Fixture
테스트 픽스처는 테스트를 실행하기 위해 미리 준비해 두는 고정된 상태나 데이터다. 매 테스트마다 같은 객체를 만드는 코드를 @BeforeEach나 픽스처 생성 메서드(MemberFixture.create())로 모아 두면 중복이 줄고, 테스트 본문에는 검증하려는 내용만 남는다. GoogleTest 문서의 test fixture 설명도 같은 데이터 구성을 여러 테스트에서 재사용하는 개념이다. 다만 픽스처가 너무 많은 것을 숨기면 테스트를 읽을 때 어떤 전제에서 출발하는지 알기 어려워진다.
- https://stackoverflow.com/questions/12071344/what-are-fixtures-in-programming
- https://zorba91.tistory.com/304
Testing Web in Spring Boot
Spring 가이드는 웹 계층을 테스트하는 방법을 단계적으로 보여 준다. @SpringBootTest(webEnvironment = RANDOM_PORT)와 TestRestTemplate으로 실제 서버를 띄워 HTTP 요청을 보내는 방법, @AutoConfigureMockMvc로 서버 없이 MockMvc를 쓰는 방법, @WebMvcTest로 웹 계층만 띄우고 서비스는 @MockBean으로 대체하는 방법이다. 아래로 갈수록 띄우는 범위가 좁아져 빠르다.
- https://dzone.com/articles/mockito-pros-cons-and-best
- https://spring.io/guides/gs/testing-web/
- https://velog.io/@swchoi0329/Spring-Boot-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%BD%94%EB%93%9C-%EC%9E%91%EC%84%B1
- https://blog.npcode.com/2020/02/29/%ec%bb%a4%eb%b2%84-%eb%b6%88%ea%b0%80%eb%8a%a5%ed%95%9c-%ec%bd%94%eb%93%9c/
- https://github.com/google/googletest/blob/main/docs/primer.md#test-fixtures-using-the-same-data-configuration-for-multiple-tests-same-data-multiple-tests
테스팅 환경 분리
application.yml에서 프로퍼티를 test로 하나 만들어서 테스팅을 진행해도 될 것 같다.
구체적으로는 src/test/resources/application-test.yml(또는 application.yml의 test 프로파일 문서)에 테스트용 DB 주소나 외부 API 설정을 두고, 테스트 클래스에 @ActiveProfiles("test")를 붙인다. src/test/resources/application.yml을 두면 테스트 실행 시 클래스패스에서 메인 설정보다 먼저 잡혀 메인 설정을 통째로 가리게 되므로, 일부만 덮어쓰려면 프로파일 방식이 더 안전하다.
- https://www.baeldung.com/spring-boot-testing-configurationproperties
- https://www.baeldung.com/spring-yaml
- https://stackoverflow.com/questions/38711871/load-different-application-yml-in-springboot-test
- https://myclude.tistory.com/entry/applicationyml-%ED%85%8C%EC%8A%A4%ED%8C%85-%ED%99%98%EA%B2%BD-%EB%B6%84%EB%A6%AC
@SpringBootTest vs @WebMvcTest vs @DataJpaTest
@SpringBootTest는 애플리케이션 전체 컨텍스트를 띄우는 통합 테스트용이다. @WebMvcTest는 컨트롤러, 필터, @ControllerAdvice 같은 웹 계층 빈만 띄우고 서비스와 리포지토리는 띄우지 않는다. @DataJpaTest는 엔티티와 리포지토리, EntityManager만 띄우고 기본적으로 내장 DB로 바꾸며 각 테스트를 트랜잭션 안에서 실행한 뒤 롤백한다. 테스트하려는 계층에 맞는 슬라이스를 고르면 컨텍스트 로딩 시간이 줄어든다.
- https://stackoverflow.com/questions/59097035/springboottest-vs-webmvctest-datajpatest-service-unit-tests-what-is-the-b
- https://velog.io/@jwkim/spring-boot-datajpatest-springboottest
스프링 컨테이너
스프링 컨테이너는 빈 객체의 생성, 의존성 주입, 생명주기를 관리하는 IoC 컨테이너다. 가장 기본 인터페이스는 BeanFactory이고, 실제로는 메시지 소스, 이벤트 발행, 리소스 로딩 등을 더한 ApplicationContext를 쓴다. 예전에는 XML(applicationContext.xml)로 빈을 정의했고, 지금은 @Configuration과 컴포넌트 스캔으로 정의한다.
- https://velog.io/@ehdrms2034/Spring-MVC-Application-Context.xml
스프링 빈 라이프 사이클
빈은 생성, 의존성 주입, 초기화 콜백, 사용, 소멸 콜백 순서로 생명주기를 거친다. 초기화와 소멸 콜백을 지정하는 방법은 세 가지다. @PostConstruct/@PreDestroy 애너테이션, InitializingBean.afterPropertiesSet()/DisposableBean.destroy() 인터페이스, 그리고 @Bean(initMethod = ..., destroyMethod = ...) 설정이다. 여러 방식을 함께 쓰면 애너테이션, 인터페이스, 설정 메서드 순으로 호출된다. 스프링 인터페이스에 의존하지 않는 애너테이션 방식이 가장 많이 쓰이고, 코드를 고칠 수 없는 외부 라이브러리는 @Bean 설정 방식을 쓴다.
- https://howtodoinjava.com/spring-core/spring-bean-life-cycle/#:~:text=Spring%20framework%20provides%20the%20following,methods%20in%20bean%20configuration%20file
자바 콜백 메서드
콜백 메서드는 다른 코드에 넘겨져서, 그 코드가 적절한 시점에 대신 호출해 주는 메서드다. 자바에는 함수 포인터가 없으므로 인터페이스를 구현한 객체(익명 클래스나 람다)를 넘기는 방식으로 표현한다. Comparator를 sort에 넘기는 것, JdbcTemplate에 RowMapper를 넘기는 것, 비동기 작업 완료 후 실행할 리스너를 등록하는 것이 모두 콜백이다.
- https://stackoverflow.com/questions/19405421/what-is-a-callback-method-in-java-term-seems-to-be-used-loosely#:~:text=A%20callback%20method%20in%20java%20is%20a%20method%20that%20gets,E%20(see%20example%201).
Maven vs Gradle
기능도 차이가 없고, XML보다 훨씬 보기 깔끔하게 자바코드로 구성이 되어있고, 성능도 훨씬 빠르다고 하니 Gradle로 바꾸지 않을 이유가 별로 없는 것 같다.
정확히는 Gradle 빌드 스크립트는 자바가 아니라 Groovy나 Kotlin DSL로 작성한다. 성능에 대해서는 Gradle 공식 비교 페이지가 그 이유로 세 가지를 든다. 태스크의 입력과 출력을 추적해 바뀐 부분만 다시 실행하는 증분 빌드, 같은 입력의 빌드 결과를 다른 빌드나 다른 머신과 재사용하는 빌드 캐시, 빌드 정보를 메모리에 유지하는 Gradle 데몬이다. 같은 페이지는 Gradle이 거의 모든 경우 최소 두 배 빠르다고 주장하는데, Gradle 측의 주장이라는 점은 감안해야 한다. Maven은 정해진 라이프사이클과 관례가 강해서 프로젝트 구조가 예측 가능하다는 장점이 있다.
- https://hyojun123.github.io/2019/04/18/gradleAndMaven/
- https://www.geeksforgeeks.org/difference-between-gradle-and-maven/#:~:text=Gradle%20is%20based%20on%20developing,pure%20Java%20language%2Dbased%20software.&text=It%20uses%20a%20Groovy%2Dbased,XML)%20for%20creating%20project%20structure.
- https://gradle.org/maven-vs-gradle/
- https://bkim.tistory.com/13
- https://stackify.com/gradle-vs-maven/
fork한 레파지토리 잔디 심기
GitHub 문서에 따르면 포크한 저장소에서 만든 커밋은 프로필의 기여 그래프에 집계되지 않는다. 집계되게 하려면 Pull Request로 원본 저장소에 병합되어야 한다. 그래서 포크한 저장소를 내 작업 공간으로 계속 쓰고 싶다면, 새 저장소를 만들고 기존 저장소를 미러링(git clone --bare 후 git push --mirror)해서 포크 관계가 없는 독립 저장소로 옮기는 방법을 쓴다.
- https://soranhan.tistory.com/11
댓글
아직 댓글이 없습니다