
인프런 워밍업 클럽 3기 BE 스터디 4주차
💻 강의
입문자를 위한 Spring Boot with Kotlin - 나만의 포트폴리오 사이트 만들기
📚 학습
@Transactional(readOnly = true)
읽기 전용 트랜잭션으로 설정하면 데이터 변경이 일어나지 않기 때문에 스냅샷을 저장하는 동작을 생략해 좀 더 성능을 개선할 수 있다
Service 테스트
@ExtendWith : 테스트 확장을 지원하며 JUniit5와 Mockito를 연동해 테스트를 진행할 경우에는 MockitoExtension.class를 사용
@InjectMocks : Mockito에서 테스트 대상이 되는 클래스에 인스턴스를 주입하기 위해 사용
@Mock : Mockito에서 Mock 객체를 생성할 때 사용하며 실제로 메서드는 갖고 있지만 내부 구현이 없는 상태
Repository 테스트를 했을 때와는 다르게 Service 테스트를 할 때는 Mockito를 사용한다
@ExtendWith(MockitoExtension::class)
class PresentationServiceTest {
@InjectMocks
lateinit var presentationService: PresentationService
@Mock
lateinit var presentationRepository: PresentationRepository
}
Service 테스트 코드 해석
given
일단 홀수이면 isActive = false, 짝수이면 true로 설정한다
필터링을 해 isActive = true인 것만 남긴다
presentationRepository.getActiveIntroductions()를 실행하면 필터링 한 데이터를 반환하도록 한다
["2", "4", "6"]
when
테스트 대상 메서드를 실행한다
then
DATA_SIZE = 7이므로 DATA_SIZE / 2 = 3.5이지만 정수 연산에서는 3이 반환 된다
필터링 한 데이터 수가 일치하면 첫 번째 검증은 통과이다
content 값을 정수로 변환하고, isEven()을 사용해 짝수인지 검증한다
짝수이면 두 번째 검증도 통과이다
@Repository
class PresentationRepository(
...) {
...
fun getActiveIntroductions(): List<Introduction> {
return introductionRepository.findAllByIsActive(true)
}
...
@Test
fun testGetIntroductions() {
// given
val introductions = mutableListOf<Introduction>()
for (i in 1..DATA_SIZE) { // 1, 3, 5, 7 -> false / 2, 4, 6 -> true
val introduction = Introduction(content = "${i}", isActive = i % 2 == 0)
introductions.add(introduction)
}
val activeIntroductions = introductions.filter { introduction ->
introduction.isActive
}
Mockito.`when`(presentationRepository.getActiveIntroductions())
.thenReturn(activeIntroductions)
// when
val introductionDTOs = presentationService.getIntroductions()
// then
assertThat(introductionDTOs).hasSize(DATA_SIZE / 2)
for (introductionDTO in introductionDTOs) {
assertThat(introductionDTO.content.toInt()).isEven()
}
}
Controller 테스트
@SpringBootTest : 실제 애플리케이션과 유사한 환경을 구성하여 테스트 가능
@AutoConfigureMockMVC : Spring MVC를 모의로 테스트하는 데 사용
@SpringBootTest
@AutoConfigureMockMvc
@DisplayName("[API 컨트롤러 테스트]")
class PresentationApiControllerTest(@Autowired private val mockMvc: MockMvc) {
}
Thymeleaf 문법
th:fragment : 템플릿의 일부를 재사용 가능한 fragment로 정의
th:replace : 해당 요소를 다른 요소로 대체할 때 사용
fragment 이름을 navigation으로 지정하고, 해당 경로에 있는 파일의 navigation fragment를 찾아서 대체한다
// /templates/presentation/fragments/fragment-navigation.html
<nav class="navbar navbar-expand-lg navbar-light bg-white py-3" th:fragment="navigation">
// /templates/presentation/index.html
<div th:replace="~{presentation/fragments/fragment-navigation :: navigation}"></div>
🎯 미션 6과 미션 7
가상 프로필을 나의 프로필로 바꾸기
강의 실습 프로젝트의 데이터 초기화 클래스 내용을 나의 프로필로 바꾼 뒤 커밋
커밋 메시지 : [미션6] 가상 프로필을 나의 프로필로 바꾸기
미션6 제출 스레드에 깃허브 커밋 링크를 공유
프로젝트를 배포한 뒤 브라우저에서 접속
미션7 제출 스레드에 도메인 주소를 공유
문제
이미지 태그를 지정하고 Dockerfile을 Build 하는 과정에서 test 실패 오류가 발생하였다
테스트 코드가 문제인 건지 확인하기 위해 TestApplication을 실행해 전체 테스트 코드를 실행시키니 오류는 없었다
원인을 찾을 수 없어서 계속 구글링을 해보니 아래 코드가 원인이 될 수도 있다고 한다
tasks.withType<Test> {
useJUnitPlatform()
}
해결
아래 코드를 주석 처리하니 정상적으로 Build가 됐다
tasks.withType<Test> {
useJUnitPlatform()
}
댓글을 작성해보세요.