Study/test

[Kotlin] 테스트 코드 쉽게 작성하기 ( KotlinFixture, MockK, LiveTemplates )

에디개발자 2021. 12. 27. 07:00
반응형

나를 닮았다고 한다....

개인적으로 테스트 코드는 중요하다고 생각합니다. 하지만 다양한 이유로 테스트 코드는 많이 Skip 됩니다.

  • 시간적 여유가 없을 때
  • 테스트 코드를 위한 노가다 작업이 많을 때
  • 그 외 등등...

이 방법을 백퍼센트 보완하진 못하지만 좀 더 용이하게 작성하기 위해 제가 사용하는 방법을 공유합니다. 


목차

  1. MockK
  2. KotlinFixture
  3. Live Templates

 

1. MockK

Java 진영에는 Mockito가 있다면 Kotlin 진영에서는 Mockk가 있습니다. 

Mockk는 Java에서 Mock처리하는 것과 동일하게 Kotlin스럽게 Mock을 처리할 수 있도록 도와줍니다.

 

환경구성

 build.gradle.kts

testImplementation 'com.ninja-squad:springmockk:{version}'

// 1)
sourceSets {
    test.java.srcDirs += 'src/test/kotlin'
}

1) Java Project일 경우 kotlin 폴더를 생성하여 하위에 테스트 코드를 작성하려할 때 이 설정을 넣어주면 자동으로 kotlin 폴더가 테스트 폴더로 지정됨

Project Structure > Modules에서 직접 test folder 지정할 경우 모든 개발자가 이 설정을 해줘야하며 intellij를 재부팅해도 재설정해줘야하는 불편함이 있음

 

Mockito vs MockK

Mockito 소스와 Mockk 소스를 비교해보겠습니다.

Mockito

@ExtendWith(MockitoExtension.class)
public class TestJava {
    
    @Mock
    private TestObject testObject;
    
    private TestService testService;
    
    @BeforeEach
    public void setUp() {
        testService = new TestService(testObject);
    }
    
    @Test
    public void runTest() {
        // given
        String result = "Run!!!";
        given(testObject.run()).willReturn(result);
        given(testObject.temp())
        
        // when
        String actual = testService.runTestObject();

        // then
        assertThat(actual).isEqualTo(result);
        verify(testObject, times(1)).temp();
    }
}

 

MockK

@ExtendWith(MockKExtension::class)
class TestKotlin {
    @MockK
    private lateinit var testObject: TestObject
    
    private lateinit var testService: TestService
    
    @BeforeEach
    fun setUp() {
        testService = TestService(testObject)
    }
    
    @Test
    fun `test`() {
        // given
        val result = "Run!!!"
        every { testObject.run() } returns result
        every { testObject.temp() } just runs
        
        // when
        val actual = testService.runTestObject()
        
        // then
        assertThat(actual).isEqualTo(result)
        verify(exactly = 1) { testObject.temp() }
    }
}

 

주요 비교 Point!

// java
given(testObject.run()).willReturn(result);
given(testObject.temp())

verify(testObject, times(1)).temp();

// kotlin
every { testObject.run() } returns result
every { testObject.temp() } just runs

verify(exactly = 1) { testObject.temp()}

Kotlin 진영은 Java 진영에서 가독성을 높게 보완하였습니다.

테스트코드 또한 마찬가지로 MockK를 사용한다면 높은 가독성을 가지는 테스트 코드를 작성할 수 있습니다.

 

2) KotlinFixture

Test Data의 생성을 쉽게 도와주는 라이브러리입니다.

 

환경구성

build.gradle.kts

testImplementation 'com.appmattus.fixture:fixture:{version}'

 

활용 방법

간단하게 선언하여 사용가능

// kotlin
val fixture = kotlinFixture()

every { sampleRepository.findById(any()) } returns
    fixture()

val fixture = kotlinFixture {}

// kotlin에서 java 코드 test
val fixture = Fixture()

every { sampleRepositroy.findById(any()) } returns
    fixture()

 

데이터 클래스의 원하는 필드를 설정하여 사용가능

data class Market(
    val name: String,
    val address: Address,
    val customers: List<String>
)

data class Address(
    val address1: String,
    val address2: String,
    val postNumber: String,
)

// 사용
val fixture = kotlinFixture {
    nullabilityStrategy(NeverNullStrategy)
    optionalStrategy(NeverOptionalStrategy)
}

val market: Market = fixture()
// result
// Market(name=13e5ddd1-c88c-491d-a4ef-e745f1f6c38d, address=Address(address1=640b4dab-6a88-4bb8-9c60-50a81e12bd37, address2=0e58eb58-e785-4754-8cd6-35b32d4d5889, postNumber=a106cd6a-91db-4937-bd44-9aebc1eccd78), customers=[1df56ad6-0ef2-462e-884b-191a3450a6e2, 0cfb0a90-6919-4365-90c6-4c0475ce9c7e, b80b18da-d2c0-4d13-9ee9-6880a7731b72, a3802105-ca82-4034-913d-e1d7828d8ce2, a78f2ff3-d1e6-4738-afff-b482c9373d18])

// 특정 필드 지정
val market2: Market = fixture { property(Market::name} { "마케엣!" }
// result
// Market(name=마케엣!, address=Address(address1=3adff601-3489-45ac-9074-689fb67fe2bd, address2=59d94ca2-0d48-4724-a61b-269e30e10222, postNumber=5c10ebd4-12a8-4b02-8f3c-ac2377c9737f), customers=[64588699-bd6c-4c06-8aa5-2e566618e228, 945f008c-38b2-4b78-830d-dda7389dc0ff, fb76706f-1154-43ac-b7a6-39964cc47592, 2be0479c-eb84-4351-82e2-f6c2f151c63c, 89ee55cc-9972-4d60-880a-c508e7d3ffbc])

 

주의사항!

data class의 속성 접근자가 private인 경우 위 방법은 사용할 수 없습니다. 아래와 같이 속성값을 하드코딩해야합니다.

data class Market(
    private val name: String,
    private val address: Address,
)


val fixture = kotlinFixture {
    nullabilityStrategy(NeverNullStrategy)
    optionalStrategy(NeverOptionalStrategy)
}

val market2: Market2 = fixture {
    property<Market2, String>("name") {"Market!!"}
}

println(market2)

 

다양한 방법으로 활용

두 가지 모두 보완하도록 작성

  1. kotlinFixture를 이용할 수 있는 메서드 제공
  2. private 일 경우 하드코딩 없이 객체 생성 메서드 제공 ( private인 경우가 많진 않겠지만.. )
// fixture config
private val fixture = kotlinFixture {
    nullabilityStrategy(NeverNullStrategy)
    optionalStrategy(NeverOptionalStrategy)
}

// object fixture
class MarketFixture {
    companion object {
        // kotlinFixture 기능 활용  - (1)
        fun generate(c: ConfigurationBuilder.() -> Unit) = fixture<Market> { c }

        // 일반 메서드 제공           - (2)
        fun generate(
            name: String = fixture(),
            address: Address = fixture(),
            customers: List<String> = fixture()
        ) = Market(
            name = name,
            address = address,
            customers = customers
        )
    }
}

fun test() {
    // (1)
    val a = MarketFixture.generate { 
        property(Market::name) {"Market!!!"}
    }
    
    // (2)
    val b = MarketFixture.generate(name = "Market!!!!")
}

 

3. Live Templates

Intellij의 LiveTemplates 의 기능을 이용하여 반복적인 작업을 처리합니다.

 

반복적인 코드 적용

BeforeEach

// live templates
@BeforeEach
fun setUp() {
    $END$
}

// code
// tsetup
@BeforeEach
fun setUp() {
    
}

 

Nested Class

// live templates
@Nested
@DisplayName("$CLASS_DESCRIPTION$")
inner class $CLASS_NAME$ {
    $END$
}

// code
// tnested
@Nested
@DisplayName("")
inner class  {
  
}

 

Test Method

// live templates
@Test
fun `$METHOD_NAME$`() {
    
}

// code
// tdd
@Test
fun ``() {
    
}

 

assert

// live templates
// assertall
assertAll(
    { assertThat($A$).isEqualTo($B$) }
)

// asserteq
assertThat($A$).isEqualTo($B$)

// assertsize
assertThat($A$).hasSize($B$)



// code
// asserteq
assertThat().isEqualTo()


// assertall
assertAll(
    { assertThat().isEqualTo() }
)

// assertsize
assertThat().hasSize()
반응형