[2026] Kotlin 테스팅 | JUnit, MockK, 테스트 작성법

[2026] Kotlin 테스팅 | JUnit, MockK, 테스트 작성법

이 글의 핵심

import org.junit.jupiter.api.Test import kotlin.test.assertEquals.

들어가며

JUnit·Kotest 등과 함께 쓰면 given/when/then 스타일을 짧게 쓰기 좋습니다. 테스트는 프로덕션 코드와 같은 멀티플랫폼 모듈에 둘 수도 있습니다.

실무에서 마주한 현실

개발을 배울 때는 모든 게 깔끔하고 이론적입니다. 하지만 실무는 다릅니다. 레거시 코드와 씨름하고, 급한 일정에 쫓기고, 예상치 못한 버그와 마주합니다. 이 글에서 다루는 내용도 처음엔 이론으로 배웠지만, 실제 프로젝트에 적용하면서 “아, 이래서 이렇게 설계하는구나” 하고 깨달은 것들입니다. 특히 기억에 남는 건 첫 프로젝트에서 겪은 시행착오입니다. 책에서 배운 대로 했는데 왜 안 되는지 몰라 며칠을 헤맸죠. 결국 선배 개발자의 코드 리뷰를 통해 문제를 발견했고, 그 과정에서 많은 걸 배웠습니다. 이 글에서는 이론뿐 아니라 실전에서 마주칠 수 있는 함정들과 해결 방법을 함께 다루겠습니다.

1. JUnit 기본

프로젝트 설정

아래 코드는 kotlin를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// build.gradle.kts
dependencies {
    testImplementation("org.junit.jupiter:junit-jupiter:5.9.0")
    testImplementation("org.jetbrains.kotlin:kotlin-test")
}

기본 테스트

다음은 kotlin를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
class CalculatorTest {
    
    @Test
    fun `덧셈 테스트`() {
        val result = add(2, 3)
        assertEquals(5, result)
    }
    
    @Test
    fun `뺄셈 테스트`() {
        val result = subtract(10, 3)
        assertEquals(7, result)
    }
}
fun add(a: Int, b: Int) = a + b
fun subtract(a: Int, b: Int) = a - b

Assert 메서드

아래 코드는 kotlin를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

import kotlin.test.*
class AssertTest {
    
    @Test
    fun `다양한 Assert`() {
        assertEquals(5, 2 + 3)
        assertNotEquals(4, 2 + 3)
        assertTrue(5 > 3)
        assertFalse(5 < 3)
        assertNull(null)
        assertNotNull("text")
    }
}

예외 테스트

다음은 kotlin를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

import org.junit.jupiter.api.assertThrows
class ExceptionTest {
    
    @Test
    fun `0으로 나누기 예외`() {
        assertThrows<ArithmeticException> {
            divide(10, 0)
        }
    }
}
fun divide(a: Int, b: Int): Int {
    if (b == 0) throw ArithmeticException("0으로 나눌 수 없음")
    return a / b
}

2. MockK

프로젝트 설정

다음은 간단한 kotlin 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// build.gradle.kts
dependencies {
    testImplementation("io.mockk:mockk:1.13.5")
}

기본 Mock

다음은 kotlin를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

import io.mockk.*
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
interface UserRepository {
    fun findById(id: String): User?
}
data class User(val id: String, val name: String)
class UserServiceTest {
    
    @Test
    fun `사용자 조회 테스트`() {
        val repository = mockk<UserRepository>()
        every { repository.findById("1") } returns User("1", "홍길동")
        
        val service = UserService(repository)
        val user = service.getUser("1")
        
        assertEquals("홍길동", user.name)
        verify { repository.findById("1") }
    }
}
class UserService(private val repository: UserRepository) {
    fun getUser(id: String): User {
        return repository.findById(id) 
            ?: throw NoSuchElementException("User not found")
    }
}

Verify

아래 코드는 kotlin를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

@Test
fun `호출 검증`() {
    val repository = mockk<UserRepository>(relaxed = true)
    
    val service = UserService(repository)
    service.getUser("1")
    
    verify(exactly = 1) { repository.findById("1") }
    verify(atLeast = 1) { repository.findById(any()) }
}

3. 테스트 라이프사이클

Before/After

다음은 kotlin를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

import org.junit.jupiter.api.*
class LifecycleTest {
    
    @BeforeEach
    fun setup() {
        println("테스트 시작 전")
    }
    
    @AfterEach
    fun teardown() {
        println("테스트 종료 후")
    }
    
    @Test
    fun `테스트 1`() {
        println("테스트 1 실행")
    }
    
    @Test
    fun `테스트 2`() {
        println("테스트 2 실행")
    }
}

4. 실전 예제

예제: 사용자 서비스 테스트

다음은 kotlin를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

data class User(val id: String, val name: String, val email: String)
interface UserRepository {
    fun findById(id: String): User?
    fun save(user: User): User
    fun findAll(): List<User>
}
class UserService(private val repository: UserRepository) {
    fun getUser(id: String): User {
        return repository.findById(id) 
            ?: throw NoSuchElementException("User not found")
    }
    
    fun createUser(name: String, email: String): User {
        if (name.isBlank()) {
            throw IllegalArgumentException("이름은 필수입니다")
        }
        if (!email.contains("@")) {
            throw IllegalArgumentException("이메일 형식이 잘못되었습니다")
        }
        
        val user = User(
            id = generateId(),
            name = name,
            email = email
        )
        return repository.save(user)
    }
    
    fun getAllUsers(): List<User> {
        return repository.findAll()
    }
    
    private fun generateId() = System.currentTimeMillis().toString()
}
class UserServiceTest {
    private lateinit var repository: UserRepository
    private lateinit var service: UserService
    
    @BeforeEach
    fun setup() {
        repository = mockk()
        service = UserService(repository)
    }
    
    @Test
    fun `사용자 조회 성공`() {
        val expected = User("1", "홍길동", "hong@example.com")
        every { repository.findById("1") } returns expected
        
        val result = service.getUser("1")
        
        assertEquals(expected, result)
        verify { repository.findById("1") }
    }
    
    @Test
    fun `사용자 조회 실패`() {
        every { repository.findById("999") } returns null
        
        assertThrows<NoSuchElementException> {
            service.getUser("999")
        }
    }
    
    @Test
    fun `사용자 생성 성공`() {
        val user = User("1", "홍길동", "hong@example.com")
        every { repository.save(any()) } returns user
        
        val result = service.createUser("홍길동", "hong@example.com")
        
        assertEquals("홍길동", result.name)
        verify { repository.save(any()) }
    }
    
    @Test
    fun `이름 없이 생성 실패`() {
        assertThrows<IllegalArgumentException> {
            service.createUser("", "hong@example.com")
        }
    }
    
    @Test
    fun `잘못된 이메일로 생성 실패`() {
        assertThrows<IllegalArgumentException> {
            service.createUser("홍길동", "invalid-email")
        }
    }
    
    @Test
    fun `모든 사용자 조회`() {
        val users = listOf(
            User("1", "홍길동", "hong@example.com"),
            User("2", "김철수", "kim@example.com")
        )
        every { repository.findAll() } returns users
        
        val result = service.getAllUsers()
        
        assertEquals(2, result.size)
        verify { repository.findAll() }
    }
}

정리

핵심 요약

  1. JUnit: 테스트 프레임워크
  2. MockK: Kotlin 전용 Mock 라이브러리
  3. every: Mock 동작 정의
  4. verify: 호출 검증
  5. assertThrows: 예외 테스트

테스트 팁

  • 단위 테스트: 함수/클래스 단위로 작성
  • Mock 사용: 외부 의존성 제거
  • 명확한 이름: 백틱으로 한글 테스트명 사용
  • Given-When-Then: 테스트 구조화

다음 단계


관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3