본문 바로가기

TIL

TIL) Kotlin Test

https://mockk.io/

 

MockK

Provides DSL to mock behavior. Built from zero to fit Kotlin language. Supports named parameters, object mocks, coroutines and extension function mocking

mockk.io

 

아래 글은 어떤 외국 PPT 자료를 참고하였는데, 어디서 받았는지 기억이 나질 않는다..

 

 

1. 테스트용 공통 인스턴스

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TestClass{
    private val name = "TOMAS"
    
    private val age = 28
    
    @Test
    fun test1() {
	}
    
    @Test
    fun test2() {
	}
}

 

Junit 을 사용해서 테스트를 작성하면,

같은 클래스에 작성하더라도 @Test 가 붙은 메서드마다 새로운 객체로 테스트를 진행한다.

따라서 전역변수를 한번 선언하더라도 테스트의 수 만큼 생성되게 되는 것인데,

 

위와 같이 @TestInstance 의 PER_CLASS 를 사용하면 변수를 val 로 한번만 선언 후 재사용할 수 있다.

 

 

2. @Nested 에너테이션을 사용하면 계층구조의 테스트를 작성할 수 있다.

@DisplayName("자동차중")
class CarTest {

    @Nested
    @DisplayName("벤츠에서 만든 차는")
    class BenzTest { // 외부 참조가 필요하다면 inner class
    	
        @Nested
        @DisplayName("가격이")
        class PriceTest {
        
            @DisplayName("1억이 넘는다.")
            @Test
            fun test1() {
            }
            
            @DisplayName("1억이 넘지 않으면 에러다.")
            @Test
            fun test2() {
            }
        }
    }

}

이런 식으로 구성하면 깔끔한 테스트 결과창을 볼 수 있다.

 

 

 

3. 확장함수를 이용한 kotlin 중복제거

assertThat(price1).isCloseTo(0.3f, Offset.offset(EPSILON))
assertThat(price2).isCloseTo(0.2f, Offset.offset(EPSILON))
assertThat(price3).isCloseTo(0.5f, Offset.offset(EPSILON))


// 위 코드를 확장함수로 중복제거

fun AbstractFloatAssert<*>.isCloseTo(expected: Float)
 = this.isCloseTo(expected, Offset.offset(EPSILON))
 
assertThat(price1).isCloseTo(0.3f, EPSILON)
assertThat(price2).isCloseTo(0.2f, EPSILON)
assertThat(price3).isCloseTo(0.5f, EPSILON)

 

 

 

 

4. Mockk 을 이용해서 매 테스트마다 목킹을 새로 하기

@ExtendWith(MockKExtension::class)
class TestClass {
	
    private lateinit var payService: PayService
    private lateinit var carRepository: CarRepository
    
    @BeforeEach
    fun init() {
    	payService = mockk()
        client = mockk()
    }
}

// 위 보단 아래가 빠르다.

class TestClass {
	
    private val payService: PayService
    private val carRepository: CarRepository
    private lateinit var carService: CarService // injectMockk 이 매번 필요하면 이것만 lateinit
    
    @BeforeEach
    fun init() {
        clearMocks(payService, carRepository)
        carService = CarService(payService, carRepository)
    }
}

 

매 테스트마다 목킹을 새로 할 때 init() 에서 mocking 을 하는 것 보단

mockk 라이브러리에서 제공하는 clearMocks() 를 사용하면 더 빠르다.

 

덤으로 val 로 쓸 수 있다는 장점도 있다.

 

 

 

5. data class assertion

data class 는 알다시피 equals() 를 자동으로 적절히 오버라이딩해준다.

 

따라서 아래와 같은 테스트들이 가능해서 편리하다.

 

student 는 data class

val expected = Student(id = 2, userId = 9, name = "Cat")
assertThat(actual).isEqualTo(expected)

assertThat(actualList).containsExactly(
    Student(id = 1, userId = 9, name = "Cat"),
    Student(id = 2, userId = 4, name = "Dog")
)

assertThat(actual)
    .isEqualToIgnoringGivenFields(expected,"id")

assertThat(actual)
    .isEqualToComparingOnlyGivenFields(expected,"name")

assertThat(actualList)
    .usingElementComparatorIgnoringFields("id")
    .containsExactly(expected1, expected2)

assertThat(actualList)
    .usingElementComparatorOnFields("name")
    .containsExactly(expected1, expected2)

 

 

 

 

6. 그리고 객체의 디폴트값을 설정할 수 있음을 활용한 객체 생성 메서드를 사용하면 좋다.

 

fun createStudent(
    id: Int = 1,
    name: String = "Tomas",
    grade: Int = 1,
    desc: String = "Hi" // 여기 네개들은 바뀌어도 되고 안바뀌어도 될 값들.
) = Student(
    id = id,
    school = "Koltion Junior School", // 테스트용 객체들 중 고정될 값.
    name = name,
    grade = grade,
    desc = desc
)

@Test
fun test1() {
	val stud1 = createStudent(id = 3, name = "Brouce")
    val stud2 = createStudent(id = 4, name = "Ruller", grade = 3)
    
    // 중복된 데이터 없이 딱 바뀔 값들만 선언할 수 있다.
}

 

 

 

 

7. Data class 의 @ParameterizedTest

data class TestData(
    val input: String, // 테스트로 넣을 인자
    val expected: Order // 인자를 넣었을 때 예상결과
)

@ParameterizedTest
@MethodSource("getTestDataList")
fun varifyList(testData: TestData) {

    assertThat(findItem(testData.input)) // when
        .isEqualTo(testData.expected) // then
}

// given
private fun getTestDataList() = Stream.of(
    TestData(input = "MacBook", expected = Order(13, "MacBook", 3000000)),
    TestData(input = "Airpod", expected = Order(24, "Airpod", 320000)),
    TestData(input = "AppleWatch", expected = Order(55, "AppleWatch", 500000)),
    TestData(input = "IPad", expected = Order(16, "IPad", 1500000))
)

 

 

 

https://phauer.com/2019/modern-best-practices-testing-java/

 

Modern Best Practices for Testing in Java

Modern best practices for unit tests and integration tests in Java and in general

phauer.com

https://resources.jetbrains.com/storage/products/kotlinconf2018/slides/4_Best%20Practices%20for%20Unit%20Testing%20in%20Kotlin.pdf

 

https://techblog.woowahan.com/5825/

 

스프링에서 코틀린 스타일 테스트 코드 작성하기 | 우아한형제들 기술블로그

{{item.name}} 안녕하세요 저는 공통시스템개발팀에서 플랫폼 개발을 담당하고 있는 김규남이라고 합니다. 이 글은 올해 사내에서 진행한 코틀린 밋업에서 스프링에서 코틀린 스타일 테스트 코드

techblog.woowahan.com

 

'TIL' 카테고리의 다른 글

TIL) Airflow Xcom  (0) 2022.02.25
TIL) EPSILON, 몽고 업데이트시 다른 컬럼 값 참조, 몽고 덤프  (0) 2022.02.24
TIL) 인덱스, DFA  (0) 2022.02.23
TIL) Webserver vs WAS, NGNIX vs Apache  (0) 2022.02.22
TIL) associate 시리즈  (0) 2022.02.19