Skip to content

코틀린으로 Unit 테스트하는 방법

Sim-km edited this page Apr 18, 2024 · 1 revision

자바에서 코틀린으로 변경하는 과정에서 테스트 코드를 다시 작성할 때, 코틀린 테스트 라이브러리를 잘 활용하기 정리했다.

우선 코틀린 진영에는 자바에서의 JUnit과 코틀린의 Kotest등을 활용해서 테스트 코드를 작성한다.

JUnit과 Kotest를 고민하던 중, Kotest의 경우 테스트 작성 시 좀 더 직관적인 코드 작성이 가능하다는 점에서 선택하게 됐다.

Kotest

Kotest는 코틀린으로 작성된 테스트 프레임워크이다.

Kotest는 여러개의 하위 프로젝트로 나뉘어 관리되고 있다.

  • Test Framework
  • Assertions library
  • Property Testing

Kotest는 junit 기반으로 작성되어 있다.

공식 문서 : https://kotest.io/

gradle

Kotest를 사용하기 위해 gradle 의존성을 추가해줘야 한다.

testImplementation("io.kotest:kotest-runner-junit5:$version")  // Test Framework
testImplementation("io.kotest:kotest-assertions-core:$version") // Assertions Library
testImplementation("io.kotest:kotest-property:$version") // 

작성 방법

일반적인 JUnit을 활용해 given, when,then 스타일로 작성한 테스트 코드는 다음과 같다

public class UserAuthorityGetServiceTest{

    @MockBean
    private UserReader userReader;

    private UserAuthorityGetService useAuthorityGetService = new UserAuthrotiyGetService(userReader, ...);

    @Test
    @DisplayName("사용자 권한을 조회한다.")
    void getUserAuthority(){
        // given
        Long userId = 1L;
        User user = new User(userId, ...);
        BDDMockito.given(userReader.readByUserId(userId)).willReturn(user);
        // when
        UserAuthorityInfoResponse authority = userAuthorityGetService.getUserAuthority();
        // then
        Assertions.assertThat(authority.getAuthority()).isEqualTo(user.getAuthority());
}

Kotest의 Behavior Spec을 이용하면 given, when, then을 이용해 좀 더 직관적인 테스트 코드 작성이 가능하다

class UserAuthorityGetServiceTest: BehaviorSpec({
    val userReader = mockk<UserReader>()
    val userUtils = mockk<UserUtils>()

    val userAuthorityGetService = UserAuthorityGetService(userReader, userUtils)

    given("사용자의 권한을 조회를 "){
        val userId : Long = 1
        val user: User = User(userId, ...)
        every { userUtils.getIdFromAccessUser() } returns userId
        every { userReader.readByUserId(userId) } returns user
        `when`("정상적으로 요청하면"){
            val response = userAuthorityGetService.getUserAuthority()
            then("요청한 사용자의 권한을 반환한다."){
                response.authority shouldBe userAuthority.authority // assertions
            }
        }
    }
})

Mockk

테스트 코드를 작성하다보면 mock 객체를 활용해서 특정 클래스의 반환 값을 지정해줘야할 때가 있다. 이때 mockk를 활용하면 된다.

Mockk는 자바에서의 Mockito와 비슷한 라이브러리다.

gradle

testImplementation("io.mockk:mockk:${mockkVersion}") // mockk

사용 방법

자세한 사용 방법은 mockk 공식 문서를 통해 확인하면 된다.

공식 문서 : https://mockk.io/

mocking

mockk에서 제공하는 inline 메소드인 mockk를 통해 객체를 mocking 할 수 있다. 또한 mockk() 메소드를 사용할 때, 대상 객체의 타입을 mockk() 메소드에 제네릭을 통해 지정해주거나 변수에 타입을 명시해주면 된다.

val userReader = mockk<UserReader>() // 제네릭 활용
val userReader : UserReader = mockk() // 타입 직접 명시

every

mockk에서는 every를 통해 반환 값을 지정해줄 수 있다.

val userReader = mockk<UserReader>() // 제네릭 활용

val user = ...

every { userReader.readByUserId(any()) } returns user

이때 만약 메소드 호출은 있지만 반환값이 없는 경우 every를 통해 해당 메소드가 mockk()를 반환하도록 명시해주거나 객체를 mocking 할 때, mockk 메소드에 relaxed=true를 명시해줘야 한다.

// 반환값 명시
val pathHistoryAppender = mockk<PathHistoryAppender>()
every { pathHistoryAppender.appendHistory(pathHistory)} returns mockk()


// relaxed 설정
val pathHistoryAppender = mockk<PathHistoryAppender>(relaxed = true) 

verify

verify를 통해 행위에 대해서 검증할 수 있다. 예를 들어 save 메소드가 정상적으로 호출이 되었는지, 호출된 횟수가 정확한지, 아니면 호출되지 않았는지 검증할 수 있다.

verify(exactly = 1) { pathHistoryAppender.appendHistory(pathHistory) }

FixtureMonkey

fixture monkey도 코틀린을 통해 간편해졌다.

기존 임의의 값을 가지는 인스턴스를 생성할 때, 타입을 명시해줬는데, 코틀린 버전의 fixture monkey를 사용하면 타입 명시 없이도 인스턴스 생성이 가능하다. 그 대신에 변수 선언시 타입은 명시해줘야 한다.

val user: User = FixtureMonkey.create().giveMeOne() // 메소드에 타입 명시 x