[Kotlin] Kotest

Hood·2025년 6월 5일

Kotlin

목록 보기
18/18
post-thumbnail

✍  코틀린과 친해지자

공부가 필요한 부분에 대해서 정리한 글입니다.


들어가기 전

지난 포스트에서 개발 방법론 중 TDD 에 대해서 알아보았습니다.
TDD를 한마디로 정리하면 작은 단위의 테스트 케이스를 작성하고
이를 통과하는 코드를 추가하는 단계를 반복하여 구현하는 개발 방법입니다.
그래서 Kotlin에서는 어떻게 접목하여 사용할 수 있는지에 대해 알아보도록 합시다!


Kotest

Kotest 는 Kotlin을 위한 테스트 프레임워크입니다.

Kotlin은 Java 위에서 동작하기 때문에 JUnit도 사용할 수 있지만 해당 프레임워크를 사용하게 되면
단위테스트에 특화되어 있어 한눈에 알아보기 매우 어렵습니다.
또한 테스트코드가 중복될 경우가 많고 테스트 스타일이 한정적이라는 단점이있습니다.

때문에 Kotest 를 사용해야 하며
해당 프레임워크는 Kotlin의 문법적 장점을 극대화한 테스트 프레임워크입니다.


Kotest 의존성 추가

dependencies {
	//Kotest용 의존성
	testImplementation("io.kotest:kotest-runner-junit5-jvm")
	testImplementation("io.kotest:kotest-assertions-core-jvm")
}

Kotest를 사용하기 위해서는 다음과 같은 의존성을 추가해주어야 합니다.
junit 은 Junit5 플렛폼에서 Kotest 테스트 실행을 가능하게 해주는 의존성이고
assertions-core 는 assertEqual같은 Kotest 전용 matcher를 사용하기 위해서 필요한 의존성입니다.

Kotest Matchers

Matchers 테스트에서 기본 데이터를 검증하기 위해 사용됩니다.
특히 일반적인 값을 비교하거나, 리스트/맵 등의 포함 여부와 크기, 예외 발생 여부, 타입 검증 등등
여러가지 방면에서 사용될 수 있는데 이런 Macter 함수에 대해 정리해 봅시다.

기본 값 비교 Matchers

Matcher설명예시
shouldBe(expected)값이 같아야 통과"Hello" shouldBe "Hello"
shouldNotBe(expected)값이 다르면 통과5 shouldNotBe 10
shouldBeNull()null인지 확인value.shouldBeNull()
shouldNotBeNull()null이 아니어야 통과user.shouldNotBeNull()
shouldBeTrue() / shouldBeFalse()boolean 확인flag.shouldBeTrue()

숫자/비교 관련 Matchers

Matcher설명예시
shouldBeGreaterThan(x)x보다 커야 통과5 shouldBeGreaterThan 3
shouldBeLessThan(x)x보다 작아야 통과2 shouldBeLessThan 10
shouldBeBetween(a, b)a 이상 b 이하5 shouldBeBetween(1, 10)
shouldBeIn(range)범위 안에 있는지7 shouldBeIn(1..10)

컬렉션 (List, Set, Map) 관련 Matchers

Matcher설명예시
shouldContain(x)요소 포함listOf(1, 2, 3) shouldContain 2
shouldContainExactly(...)순서까지 동일해야 통과list shouldContainExactly listOf(1, 2, 3)
shouldContainAll(...)순서는 무시하고 포함list shouldContainAll 1, 3
shouldBeEmpty() / shouldNotBeEmpty()비었는지 확인emptyList<Int>().shouldBeEmpty()
shouldHaveSize(n)크기 확인list shouldHaveSize 5

문자열 Matchers

Matcher설명예시
shouldStartWith("prefix")접두사 확인"Kotlin" shouldStartWith "Kot"
shouldEndWith("suffix")접미사 확인"Kotlin" shouldEndWith "lin"
shouldContain("word")부분 문자열 포함"hello world" shouldContain "world"
shouldMatch(Regex)정규식 매칭"abc123" shouldMatch "\w+\d+"

예외 검사 Matchers

Matcher설명예시
shouldThrow<T>()예외가 발생해야 통과shouldThrow<IllegalArgumentException> { ... }
shouldThrowExactly<T>()정확히 그 예외만 통과shouldThrowExactly<IOException> { ... }
shouldFail실패를 기대할 때shouldFail { throw IllegalStateException() }

타입 검사 Matchers

Matcher설명예시
shouldBeInstanceOf<T>()특정 타입인지 확인user shouldBeInstanceOf User::class
shouldNotBeInstanceOf<T>()특정 타입이 아니어야 통과user shouldNotBeInstanceOf Admin::class

더 자세하게는 Kotest Core Matcher 공식 문서 에 나와있으며
앞에 Type.Matcher 함수와 같이 표현할 수도 있습니다.


MockK


출처

MockK는 Kotlin에 특화된 모킹(Mocking) 라이브러리입니다.
테스트 환경에서 의존성을 가짜(mock)로 만들면서 외부 시스템 없이 원하는 동작을
시뮬레이션할 수 있습니다.
사용하는 이유는 매번 프로젝트를 실행하여 해당 서비스 로직을 매번 테스트할 수 없고
테스트를 한다면 복구시키기 위해 해당 데이터를 삭제해야할 것입니다.
그렇기에 Mock이라는 가짜 객체를 만들어 테스트를 실행하는 것입니다.

mockk() 핵심 함수

함수설명예시
mockk()일반 클래스 모킹val repo = mockk<UserRepository>()
every { ... } returns ...동기 메서드 모킹every { repo.findById(1) } returns user
verify(...) { ... }메서드 실행 횟수 검증verify(exactly = 호출횟수) { memberRepository.save(member) }
slot<T>()인자로 넘겨진 값 캡처val slot = slot<User>() verify { repo.save(capture(slot)) }

비동기 mockk() 함수

함수설명예시
coEvery { ... } returns ...suspend 함수 모킹coEvery { repo.getUser(id) } returns user
Mono.just(...), Flux.just(...)Mono/Flux 모킹every { repo.findAll() } returns Flux.just(user1, user2)
coVerify { ... }비동기 호출 검증coVerify { repo.save(any()) }

비동기 테스트 예제

만약 장소를 저장하는 Repository에 Flux<Place>로
repo.findAll() 메서드를 통해 모든 장소를 불러오는 서비스로직이 있습니다.

class PlaceService(private val repo: PlaceRepository) {
    fun getAllPlaces(): Flux<Place> = repo.findAll()
}

그렇다면 해당 서비스에 대한 테스트코드는 다음과 같이 작성됩니다.

class PlaceServiceTest : StringSpec({
    val repo = mockk<PlaceRepository>()
    val service = PlaceService(repo)

    "getAllPlaces는 모든 장소를 반환한다" {
        val places = listOf(Place("1", "Cafe"), Place("2", "Park"))
        every { repo.findAll() } returns Flux.fromIterable(places)

        StepVerifier.create(service.getAllPlaces())
            .expectNextSequence(places)
            .verifyComplete()

        verify { repo.findAll() }
    }
})

📌결론

TDD를 Kotlin에 적용하기 위한 assertions-core의 함수들을 알아보았습니다.
이를 바탕으로 테스트 커버리지를 100%로 만드는 노력을 해야겠습니다.

profile
달을 향해 쏴라, 빗나가도 별이 될 테니 👊

0개의 댓글