@DataJpaTest를 이용해서 단위 테스트 코드 작성하기
2024. 6. 21. 21:06ㆍTIL
✔오늘 배운 중요한 🔑 point
- 테스트 코드 작성시 테스트용 applicaion.yml 파일을 하나 더 생성해줘야한다
- @DataJpaTest 어노테이션을 사용하면 모든 테스트 메소드에 @Transactional 이 적용되기 때문에 save() 를 하더라도 쓰기지연 SQL 저장소에만 저장이된다.
🎯 오늘 배운 내용
테스트 코드 자동화
테스트를 수행함으로써 소프트웨어의 결함을 조기에 발견하고 완성도 높은 소프트웨어를 개발하기 위해서 테스트 코드의 작성은 필수적이다. 이러한 테스트를 테스트 코드를 통해서 자동화할 필요가 있다.
FIRST 원칙
- F (Fast)
- 테스트 코드는 빠르게 실행되어야 한다.
- I (Isolated)
- 테스트는 독립적이어야 한다. 즉, 다른 테스트나 외부 시스템을 의존하면 안된다.
- R (Repeatable)
- 테스트는 반복실행해도 동일한 결과여야한다.
- S (Self-Validating)
- 테스트는 스스로 검증되어야한다. 테스트 결과를 검증하기 위해 별도 리소스가 들어가서는 안된다.
- T (Timely)
- 테스트 코드는 가능한 빨리 작성되어야한다.
@DataJpaTest를 이용해서 테스트 코드를 작성하자!
@DataJpaTest 어노테이션은 JPA와 관련된 테스트를 작성할 때 유용하며 데이터베이스와의 상호작용을 테스트하는 데 필요한 최소한의 설정을 제공한다
기본 설정
build.gradle.kts
plugins {
id("org.springframework.boot") version "3.3.1"
id("io.spring.dependency-management") version "1.1.5"
kotlin("plugin.jpa") version "1.9.24"
kotlin("jvm") version "1.9.24"
kotlin("plugin.spring") version "1.9.24"
kotlin("kapt") version "1.9.22"
}
group = "com.example"
version = "0.0.1-SNAPSHOT"
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
repositories {
mavenCentral()
}
allOpen {
annotation("javax.persistence.Entity")
annotation("javax.persistence.MappedSuperclass")
annotation("javax.persistence.Embeddable")
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jdbc")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
// Query DSL 설정
implementation("com.querydsl:querydsl-jpa:5.0.0:jakarta")
kapt("com.querydsl:querydsl-apt:5.0.0:jakarta")
kapt("jakarta.annotation:jakarta.annotation-api")
kapt("jakarta.persistence:jakarta.persistence-api")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
//Test Code
runtimeOnly("com.h2database:h2")
testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("io.mockk:mockk:1.13.9")
testImplementation("io.kotest:kotest-assertions-core:5.7.2")
testImplementation("io.kotest:kotest-runner-junit5:5.7.2")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}
kotlin {
compilerOptions {
freeCompilerArgs.addAll("-Xjsr305=strict")
}
}
tasks.withType<Test> {
useJUnitPlatform()
}
테스트 코드는 보통 테스트 패키지 하위에 작성한다
기존 application.yml과 별도로 테스트용 application.yml파일을 하나 더 생성한다
test용 application.yml
spring:
config:
activate:
on-profile: test
datasource:
url: jdbc:h2:mem:test;MODE=MySQL;
driver-class-name: org.h2.Driver
username: sa
password:
jpa:
hibernate:
ddl-auto: create-drop
on-profile: test 설정은 test profile일 경우에만 해당 application.yml 파일을 사용하겠다는 의미이다
테스트용 MemberRepositoryTest
package com.example.sampletest.domain.member
import com.example.sampletest.domain.common.QueryDslConfig
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.context.annotation.Import
import org.springframework.test.context.ActiveProfiles
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Import(value = [QueryDslConfig::class])
@ActiveProfiles("test")
class MemberRepositoryTest @Autowired constructor(
private val memberRepository: MemberRepository
) {
@Test
fun `SearchType 이 NONE 일 경우 전체 데이터 조회되는지 확인`() {
// GIVEN
// WHEN
// THEN
}
@Test
fun `SearchType 이 NONE 이 아닌 경우 Keyword 에 의해 검색되는지 결과 확인`() {
// GIVEN
// WHEN
// THEN
}
@Test
fun `Keyword 에 의해 조회된 결과가 0건일 경우 결과 확인`() {
// GIVEN
// WHEN
// THEN
}
@Test
fun `조회된 결과가 10개, PageSize 6일 때 0Page 결과 확인`() {
// GIVEN
// WHEN
// THEN
}
@Test
fun `조회된 결과가 10개, PageSize 6일 때 1Page 결과 확인`() {
// GIVEN
// WHEN
// THEN
}
companion object {
private val DEFAULT_MEMBER_LIST = listOf(
Member(email = "sample1@naver.com", password = "aaaa", nickname = "sample1"),
Member(email = "sample2@gmail.com", password = "aaaa", nickname = "sample2"),
Member(email = "sample3@daum.net", password = "aaaa", nickname = "sample3"),
Member(email = "sample4@gmail.com", password = "aaaa", nickname = "sample4"),
Member(email = "sample5@naver.com", password = "aaaa", nickname = "sample5"),
Member(email = "sample6@daum.net", password = "aaaa", nickname = "sample6"),
Member(email = "sample7@naver.com", password = "aaaa", nickname = "sample7"),
Member(email = "sample8@gmail.com", password = "aaaa", nickname = "sample8"),
Member(email = "sample9@naver.com", password = "aaaa", nickname = "sample9"),
Member(email = "sample10@gmail.com", password = "aaaa", nickname = "sample10")
)
}
}
실제 테스트 코드 적용
package com.example.sampletest.domain.member
import com.example.sampletest.domain.common.QueryDslConfig
import com.example.sampletest.domain.member.type.MemberSearchType
import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import org.springframework.context.annotation.Import
import org.springframework.data.domain.Page
import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Pageable
import org.springframework.test.context.ActiveProfiles
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Import(value = [QueryDslConfig::class])
@ActiveProfiles("test")
class MemberRepositoryTest @Autowired constructor(
private val memberRepository: MemberRepository
) {
@Test
fun `SearchType 이 NONE 일 경우 전체 데이터 조회되는지 확인`() {
// GIVEN
memberRepository.saveAllAndFlush(DEFAULT_MEMBER_LIST) // 총 10개
// WHEN
val result1: Page<Member> = memberRepository.search(MemberSearchType.NONE,"", Pageable.ofSize(10))
val result2: Page<Member> = memberRepository.search(MemberSearchType.NONE,"", Pageable.ofSize(8))
val result3: Page<Member> = memberRepository.search(MemberSearchType.NONE,"", Pageable.ofSize(15))
// THEN
result1.content.size shouldBe 10
result2.content.size shouldBe 8
result3.content.size shouldBe 15
}
@Test
fun `SearchType 이 NONE 이 아닌 경우 Keyword 에 의해 검색되는지 결과 확인`() {
// GIVEN
memberRepository.saveAllAndFlush(DEFAULT_MEMBER_LIST)
// WHEN
val result: Page<Member> = memberRepository.search(MemberSearchType.EMAIL,"naver.com",Pageable.ofSize(10))
// THEN
result.content.size shouldBe 4
}
@Test
fun `Keyword 에 의해 조회된 결과가 0건일 경우 결과 확인`() {
// GIVEN
memberRepository.saveAllAndFlush(DEFAULT_MEMBER_LIST)
// WHEN
val result: Page<Member> = memberRepository.search(MemberSearchType.EMAIL,"kakao.com",Pageable.ofSize(10))
// THEN
result.content.size shouldBe 0
}
@Test
fun `조회된 결과가 10개, PageSize 6일 때 0Page 결과 확인`() {
// GIVEN
memberRepository.saveAllAndFlush(DEFAULT_MEMBER_LIST)
// WHEN
val result: Page<Member> = memberRepository.search(MemberSearchType.NICKNAME,"sample", PageRequest.of(0, 6))
// THEN
result.content.size shouldBe 6
result.isLast shouldBe false
result.totalPages shouldBe 2
result.number shouldBe 0
result.totalElements shouldBe 10
}
@Test
fun `조회된 결과가 10개, PageSize 6일 때 1Page 결과 확인`() {
// GIVEN
memberRepository.saveAllAndFlush(DEFAULT_MEMBER_LIST)
// WHEN
val result: Page<Member> = memberRepository.search(MemberSearchType.NICKNAME,"sample", PageRequest.of(1, 6))
// THEN
result.content.size shouldBe 4
result.isLast shouldBe true
result.totalPages shouldBe 2
result.number shouldBe 1
result.totalElements shouldBe 10
}
companion object {
private val DEFAULT_MEMBER_LIST = listOf(
Member(email = "sample1@naver.com", password = "aaaa", nickname = "sample1"),
Member(email = "sample2@gmail.com", password = "aaaa", nickname = "sample2"),
Member(email = "sample3@daum.net", password = "aaaa", nickname = "sample3"),
Member(email = "sample4@gmail.com", password = "aaaa", nickname = "sample4"),
Member(email = "sample5@naver.com", password = "aaaa", nickname = "sample5"),
Member(email = "sample6@daum.net", password = "aaaa", nickname = "sample6"),
Member(email = "sample7@naver.com", password = "aaaa", nickname = "sample7"),
Member(email = "sample8@gmail.com", password = "aaaa", nickname = "sample8"),
Member(email = "sample9@naver.com", password = "aaaa", nickname = "sample9"),
Member(email = "sample10@gmail.com", password = "aaaa", nickname = "sample10")
)
}
}
각각의 테스트 함수에 대해서 통과를 했는지 실패를 했는지를 확인할 수 있다
테스트에 실패할경우
테스트에 실패할 경우에는 빨간색 X표시가 뜬것을 확인할 수 있다
🤔 어떻게 활용할까?
test 패키지를 우클릭하고 Run Test 를 검사하면 Test 하위에 있는 모든 test code를 실행시켜 테스트를 해볼수 있다
📓 오늘의 한줄
"The test of courage comes when we are in the minority. The test of tolerance comes when we are in the majority."
- Ralph W. Sockman -
'TIL' 카테고리의 다른 글
@Transactional과 롤백 (0) | 2024.06.23 |
---|---|
@DataJpaTest 를 이용한 통합 테스트 코드 작성하기 (0) | 2024.06.22 |
Projection (0) | 2024.06.20 |
영속성 컨텍스트 (0) | 2024.06.19 |
팀프로젝트 게임 채널 관리 앱 (회고) (0) | 2024.06.18 |