728x90
반응형
setup
- dependency 추가
tasks.withType<Test>().configureEach {
useJUnitPlatform()
}
// Test Framework
testImplementation('io.kotest:kotest-runner-junit5:5.5.4')
// Assertions Library
testImplementation('io.kotest:kotest-assertions-core:5.5.4')
- Kotest intellij plugin 설치
Testing Style
- kotest는 10개의 레이아웃을 제공하며, 이 중 하나를 상속받아서 사용 가능
- 여러 테스트 프레임워크에서 영감을 받아 작성된 레이아웃도 존재
사용하면 편할 것 같은 레이아웃을 몇 개 살펴보면
- Should Spec
- Behavior Spec
- Free Spec
- Annotation Spec(Junit 스펙과 비슷하여 가져와봄)
Should Spec
- 테스트 자체를 람다로 호출하여 테스트를 생성 가능
class SampleShouldSpec: ShouldSpec({
should("return the length of the string") {
"dcb".length shouldBe 3
"".length shouldBe 0
}
// junit5 @Nested
context("String Test") {
should("return the length of the string") {
"dcb".length shouldBe 3
"".length shouldBe 0
}
should("xxx") {
// testCode
}
}
context("BigDecimal Test") {
should("return the scale of the number") {
BigDecimal("300.00").scale() shouldBe 2
BigDecimal("300.0").scale() shouldBe 1
}
}
// junit @Ignore과 동일한 효과 (x를 앞에 붙인다.)
xcontext("this block is disabled") {
should("disabled by inheritance from the parent") {
// test here
}
}
})
Behavior Spec
- BDD 스타일
class SampleBehaviorSpec: BehaviorSpec({
val calculator = Calculator()
given("calculator") {
`when`("10과 5를 더하면") {
val result = calculator.add(10, 5)
then("15가 반환된다.") {
result shouldBe 15
}
}
`when`("null 을 입력하는 경우") {
then("IllegalArgumentException 을 던진다.") {
shouldThrow<IllegalArgumentException> {
calculator.add(null, 5)
}
}
}
}
})
Free Spec
- - 키워드로 뎁스(Grouping)를 지정
- 가장 많이 사용될 것으로 예상
class SampleFreeSpec: FreeSpec({
"calculator" - {
val calculator = Calculator()
"10과 5를 더하면 15가 반환된다." {
calculator.add(10, 5) shouldBe 15
}
"null을 입력하는 경우 IllegalArgumentException을 던진다." {
shouldThrow<IllegalArgumentException> {
calculator.add(null, 5)
}
}
}
})
Annotation Spec
- Junit4/5 스펙과 비슷하게 테스트코드를 작성할 수 있음
- Junit을 사용하는 것에 비해 큰 이점을 제공하지 않으며, 마이그레이션할때 주로 이용하는 것으로 보임
class SampleAnnotationSpec: AnnotationSpec() {
@BeforeEach
fun beforeEach() {
println("Before Each")
}
@Test
fun test() {
1 shouldBe 1
}
}
Lifecycle hook
- https://kotest.io/docs/framework/lifecycle-hooks.html
- DSL 메소드를 사용하여 테스트 리스너를 생성/등록
class LifeCycleHookTest: FreeSpec({
beforeSpec() {
println("Before Spec")
}
beforeContainer() {
println("Before Container")
}
beforeEach() {
println("Before Each")
}
beforeAny() {
println("Before Any")
}
beforeTest() {
println("Before Test")
}
"컨테이너1" - {
println("컨테이너 1")
"테스트1" {
println("테스트1")
}
"테스트2" {
println("테스트2")
}
}
})
Before Spec
Before Container
Before Any
Before Test
컨테이너 1
Before Each
Before Any
Before Test
테스트1
Before Each
Before Any
Before Test
테스트2
Assertion
- kotest는 다양하고 풍부한 Assertion을 제공
- 많이 사용될 Assertion들은 샘플로 남기고 나머지는, https://kotest.io/docs/assertions/core-matchers.html 참고
"Matchers" - {
"General" {
"Hello Kotlin!" shouldBe "Hello Kotlin!"
true.shouldBeTrue()
shouldThrow<ArithmeticException> {
1 / 0
}
}
"Maps" {
val map = mapOf(1 to "one", 2 to "two", 7 to "seven")
map shouldContainKey 1
map shouldContainValue "one"
map shouldContain Pair(1, "one")
}
"Strings" {
" ".shouldBeBlank()
"".shouldBeEmpty()
"Hello Kotlin" shouldContain "Kotlin"
"Hello Kotlin" shouldHaveLength 12
"12345Hello Kotlin".shouldContainADigit()
"12345Hello Kotlin" shouldEndWith("Kotlin")
"12345Hello Kotlin" shouldStartWith "12345"
}
"Integers" {
5 shouldBeLessThan 6
7 shouldBeGreaterThan 6
5 shouldBeInRange IntRange(1, 7)
}
"Collections" {
val list = listOf(1, 2, 3, 4, 5)
list shouldContain 1
list shouldContainAll listOf(1,2,3)
}
}
Data Driven Testing
- 미리 정의된 입력&출력 Set 을 사용하여 동일한 테스트를 반복해서 실행 가능(Junit @ParameterizedTest, Spock where 기능과 동일)
- https://kotest.io/docs/framework/datatesting/nested-tests.html
- 별도의 라이브러리가 필요
testImplementation("io.kotest:kotest-framework-datatest:5.5.4")
"simple" - {
val list = listOf("1", "2", "300", "500")
withData(list) { str ->
str.shouldContainADigit()
}
}
// 중첩 가능
"nested data test" - {
val services = listOf(
"http://internal.foo",
"http://internal.bar",
"http://public.baz",
)
val methods = listOf("GET", "POST", "PUT")
withData(services) { service ->
withData(methods) { method ->
// test service against method
}
}
}
- 공식사이트에서는 depth가 이쁘게 나오는데, 실제 intellij 에서 테스트하면 위처럼 나오지 않네요,
Mockk
- 코틀린 스타일의 Mock 프레임워크
- Mockito에서 지원하는 모든 기능에 몇몇 추가 기능을 더한 Kotlin을 위한 모킹 프레임워크
- https://mockk.io/
testImplementation("io.mockk:mockk:1.13.4")
"MockkTest" {
// Mock 객체 생성 (Spy 객체는 spyk로 생성)
// 1. mockk<타입>()
val userRepository = mockk<UserRepository>()
// 2. 타입 추론
// val userRepository: UserRepository = mockk()
// Relaxed mock (mocking 하지 않은 메소드를 호출하여도 예외가 발생하지 않음)
// val userRepository = mockk<UserRepository>(relaxed = true)
// mocking
every { userRepository.save(any()) } returns User("sam", 30)
every { userRepository.save(User("tom", 20)) } throws IllegalArgumentException()
userRepository.save(User("tony", 29)) shouldBe User("sam", 30)
shouldThrow<java.lang.IllegalArgumentException> {
userRepository.save(User("tom", 20))
}
// verification
verifyAll {
userRepository.save(User("tony", 29))
userRepository.save(User("tom", 20))
}
verify(exactly = 1) { userRepository.save(User("tony", 29)) }
verify(atLeast = 1) { userRepository.save(User("tony", 29)) }
verify(atMost = 1) { userRepository.save(User("tony", 29)) }
}
- Spring 관련 테스트를 진행하기 위해서는 2가지 라이브러리 추가적으로 필요
- @MockBean, @SpyBean 기능을 활용하기 위한 Ninja-Squa/springmockk (https://github.com/Ninja-Squad/springmockk)
- 테스트 코드에 SpringExtension을 사용하기 위한 kotest-extensions-spring (https://kotest.io/docs/extensions/spring.html)
testImplementation("com.ninja-squad:springmockk:2.0.3")
implementation("io.kotest.extensions:kotest-extensions-spring:1.1.2")
@SpringBootTest
class GreetingControllerTest() : FreeSpec() {
// register SpringExtension
override fun extensions() = listOf(SpringExtension)
@Autowired
lateinit var controller: UserController
@MockkBean
lateinit var service: UserService
init {
"mockkBean 사용" - {
every { service.greet("John") } returns "Hi John"
controller.greet("John") shouldBe "Hi John"
verify { service.greet("John") }
}
}
}
참고)
728x90
반응형
'프로그래밍 노트 > TEST를 해보자' 카테고리의 다른 글
[Fixture Monkey] 테스트 생산성 향상을 위해 Fixture Monkey를 찍먹해보자 (0) | 2024.10.02 |
---|---|
[Kotest] 몇 가지 팁 (0) | 2023.10.26 |
[Mockito] @Spy, @SpyMock 스터빙(stubbing)(thenReturn vs doReturn) (0) | 2020.12.14 |
[Mockito] Mockito이용하여 테스트하기(@Mock, @Spy, @InjectMocks, @MockBean, @SpyBean) (1) | 2020.12.14 |
Mockito 어노테이션(@Mock, @InjectMocks) (0) | 2019.11.18 |