코틀린에서 JUnit 테스트 작성하기
gradle 의존성 추가
testCompile 'junit:junit:4.12'
testCompile 'org.mockito:mokito-core:1.9.5'
testCompile 'org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version'
- assertEquals() 는 두 매개 변수가 같으면 성공한다. 세번째 매개변수는 실패할 경우 출력하는 메시지 이다.
package com.rivuchk.packtpub.reactivekotlin
import org.junit.Test
import kotlin.test.assertEquals
class TestClass {
@Test // 1
fun `my first test`() { // 2
assertEquals(3, 1+2) // 3
assertEquals(3, 2+3 ,"Actual value is not equal to the expected one.") // 4
}
}
다음은 정의된 함수를 테스트 하는 예제이다.
package com.rivuchk.packtpub.reactivekotlin.chapter8
fun add(a:Int, b:Int):Int = a+b
fun substract(a:Int, b:Int):Int = a-b
fun mult(a:Int, b:Int):Int = a*b
fun divide(a:Int, b:Int):Int = a/b
///////////////////////////////////////////////////////////////
package com.rivuchk.packtpub.reactivekotlin.chapter08
//(1)
import org.junit.Test
import kotlin.test.*
class TestCalculator {
@Test
fun `addition test`() {//(2)
assertEquals(1 + 2, add(1,2))
}
@Test
fun `substraction test`() {//(3)
assertEquals(8-5, substract(8,5))
}
@Test
fun `multiplication test`() {//(4)
assertEquals(4 * 2, mult(4,2))
}
@Test
fun `division test`() {//(5)
assertEquals(8 / 2, divide(8,2))
}
}
- expect 테스트 함수는 첫 번째 매개 변수로 예상되는 값을, 두 번째 람다를 입력받는데, 람다를 실행하고 반환값과 기댓값이 같은지 비교한다.
- assertNotEquals() 메서드는 두 매개변수가 동일하면 테스트가 실패한다.
- assertTrue() 는 true를 assertFalse() 는 false를 예상한다.
- assertNull() 은 null을 assertNotNull() 은 null이 아님을 예상한다.
package com.rivuchk.packtpub.reactivekotlin.chapter08
import org.junit.Test
import java.util.*
import kotlin.test.*
class TestFunctions {
@Test
fun `expected block evaluation`() {
expect(10,{
val x=5
val y=2
x*y
})
}
@Test
fun `assert illegal value`() {
assertNotEquals(-1,Random().nextInt(1))
}
@Test
fun `assert true boolean value`() {
assertTrue(true)
}
@Test
fun `assert false boolean value`() {
assertFalse(false)
}
@Test
fun `assert that passed value is null`() {
assertNull(null)
}
@Test
fun `assert that passed value is not null`() {
assertNotNull(null)
}
}
RxKotlin에서 테스트
RxKotlin에서의 테스트는 간단하지 않을 수도 있다. ReactiveX가 상태 보다는 행동을 정의하고 JUnit 및 kotlin-test를 포함한 대부분의 테스트 프레임워크가 상태를 테스트 하는데 적합하기 때문이다.
Rxkotlin 에는 테스트용 도구가 제공되며 원하는 테스트 프레임워크와 함께 사용할 수 있다.
구독자 차단
- 예제의 구독에서는 Schedulers.computaion() 메서드를 사용 즉 여러 쓰레드를 사용하기 때문에 AtomicInteger를 통해 원자성을 보장한다.
- 주석 2에서는 blockingSubscribe를 사용했다. 보통 subscribe 연산자를 사용해 프로듀서를 구독하고 delay를 사용해 현재 스레드를 대기하도록 하는데 그것은 번거로운 일이다. blockingSubscribe는 구독이 끝날때까지(현재구독이 별도의 스레드에서 발생하더라도) 현재 실행중인 스레드를 차단하는데 이는 테스트를 작성하는데 유용하다.
@Test
fun `check emissions count` () {
val emissionsCount = AtomicInteger() // 1
Observable.range(1,10)
.subscribeOn(Schedulers.computation())
.blockingSubscribe { // 2
_ -> emissionsCount.incrementAndGet()
}
assertEquals(10,emissionsCount.get())
}
차단 연산자
첫번째 배출된 아이템 가져오기: blockingFirst()
blockingFirst 연산자는 첫 번째 항목이 배출될 때까지 호출 스레드를 차단하고 있다가 배출된 아이템을 반환한다.
@Test
fun `test with blockingFirst`() {
val observable = listOf(2,10,5,6,9,8,7,1,4,3).toObservable()
.sorted()
val firstItem = observable.blockingFirst()
assertEquals(1,firstItem)
}
single 또는 maybe에서 단일 아이템 얻기: blockingGet
single 이나 maybe를 사용할때는 blockingGet() 연산자만 사용할 수 있다. 그 이유 두 모나드가 모두 하나의 아이템만 포함할 수 있기 때문이다.
- 첫 번째 테스트에서 observable.first() 는 Single 을 반환한다.
- 두 번째 테스트에서 observable.firstElement() 는 Maybe를 반환한다.
@Test
fun `test Single with blockingGet`() {
val observable = listOf(2,10,5,6,9,8,7,1,4,3).toObservable()
.sorted()
val firstElement:Single<Int> = observable.first(0)
val firstItem = firstElement.blockingGet()
assertEquals(1,firstItem)
}
@Test
fun `test Maybe with blockingGet`() {
val observable = listOf(2,10,5,6,9,8,7,1,4,3).toObservable()
.sorted()
val firstElement:Maybe<Int> = observable.firstElement()
val firstItem = firstElement.blockingGet()
assertEquals(1,firstItem)
}
마지막 아이템 얻기: blockingLast
blockingLast 연산자는 스레드가 배출할 때 마지막으로 처리된 아이템을 반환한다.
@Test
fun `test with blockingLast`() {
val observable = listOf(2,10,5,6,9,8,7,1,4,3).toObservable()
.sorted()
val firstItem = observable.blockingLast()
assertEquals(10,firstItem)
}
모든 배출을 이터러블로 가져오기: blockingIterable 연산자
blockingIterable 연산자는 Iterable이 배출을 전달한고 다음 배출을 사용할 수 있을 때까지 반복하는 스레드를 계속 차단한다.
이 연산자는 Iterator가 소비하기 전까지 사용되지 않은 값을 큐에 넣는다. 이 경우 OutOfMemory 예외가 발생할 수 있다.
@Test
fun `test with blockingIterable`() {
val list = listOf(2,10,5,6,9,8,7,1,4,3)
val observable = list.toObservable()
.sorted()
val iterable = observable.blockingIterable()
assertEquals(list.sorted(),iterable.toList())
}
모든 배출을 순회하기: blockingForEach
blockingForEach 연산자는 배출을 대기열에 넣지 않는 특성 때문에 blockingIterable 보다 낫다 오히려 호출 스레드를 차단하고 각 배출이 처리될 때까지 기다렸다가 스레드를 진행한다.
@Test
fun `test with blockingForEach`() {
val list = listOf(2,10,5,6,9,8,7,1,4,3,12,20,15,16,19,18,17,11,14,13)
val observable = list.toObservable()
.filter { item -> item%2==0 }
observable.forEach {
item->
assertTrue { item%2==0 }
}
}
TestObserver와 TestSubscriber 소개
- 주석 1 에서는 Observable/Flowable을 구독하고 있다.
- 주석 2에서는 구독이 성공적이었는지를 assertSubscribed() 테스트를 통해 검증했다.
- 주석 3에서는 awaitTerminalEvent() 메서드를 사용해 Observable/Flowable이 실행을 완료할 때까지 스레드를 차단한다.
- 주석 4, 5에서는 Observable/Flowable이 오류없이 성공적으로 완료됬는지 테스트한다.
- 주석 6 에서는 수신한 전체배출이 20개 인지 assertValuesCount() 메서드로 테스트 한다.
- 주석 7에서는 assertValues() 메서드로 각 배출의 예상값과 실제 값을 순서대로 테스트 한다.
@Test
fun `test with TestObserver`() {
val list = listOf(2,10,5,6,9,8,7,1,4,3,12,20,15,16,19,18,17,11,14,13)
val observable = list.toObservable().sorted()
val testObserver = TestObserver<Int>()
observable.subscribe(testObserver) // 1
testObserver.assertSubscribed() // 2
testObserver.awaitTerminalEvent() // 3
testObserver.assertNoErrors() // 4
testObserver.assertComplete() // 5
testObserver.assertValueCount(20) // 6
testObserver.assertValues(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20) // 7
}
@Test
fun `test with TestSubscriber`() {
val list = listOf(2,10,5,6,9,8,7,1,4,3,12,20,15,16,19,18,17,11,14,13)
val flowable = list.toFlowable().sorted()
val testSubscriber = TestSubscriber<Int>()
flowable.subscribe(testSubscriber) // 1
testSubscriber.assertSubscribed() // 2
testSubscriber.awaitTerminalEvent() // 3
testSubscriber.assertNoErrors() // 4
testSubscriber.assertComplete() // 5
testSubscriber.assertValueCount(20) // 6
testSubscriber.assertValues(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20) // 7
}
TestScheduler 이해
interval 팩토리 메서드로 생성된 Observable/Flowable 이 5분 간격으로 100개 배출을 테스트 한다면 시간이 오래걸릴 것이다. TestScheduler 는 이러한 테스트를 효과적으로 할 수 있다. 적절한 시간만큼 앞으로 돌려 단언을 수행할 수 있기 때문이다.
예제 에서는 Observable.interval 을 사용해 5분 간격으로 배출하는 Observable을 만들고 Scheduler로 TestScheduler를 생성했다.
- 주석 1에서는 배출이 하나도 존재해서는 안되며(최초 배출까지 5분이 남았으므로) assertValuesCount(0) 으로 테스트 하고 있다.
- 주석 2에서는 advanceTimeBy 메서드로 100분을 빨리 감기한 뒤 주석 3에서 20개의 배출을 받았는지 테스트 했다.
- 주석 4에서는 advanceTimeBy 메서드로 400분을 빨리 감기한 뒤 주석 5에서 총 100건의 배출을 받았는지 테스트 했다.
@Test
fun `test by fast forwarding time`() {
val testScheduler = TestScheduler()
val observable = Observable.interval(5,TimeUnit.MINUTES,testScheduler)
val testObserver = TestObserver<Long>()
observable.subscribe(testObserver)
testObserver.assertSubscribed()
testObserver.assertValueCount(0)//(1)
testScheduler.advanceTimeBy(100,TimeUnit.MINUTES)//(2)
testObserver.assertValueCount(20)//(3)
testScheduler.advanceTimeBy(400,TimeUnit.MINUTES)//(4)
testObserver.assertValueCount(100)//(5)
}
'책 > 코틀린 리액티브 프로그래밍' 카테고리의 다른 글
10. 코틀린 개발자를 위한 스프링 웹 프로그래밍 소개 (0) | 2020.05.31 |
---|---|
9. 자원 관리와 RxKotlin 확장 (0) | 2020.05.30 |
7. RxKotlin의 스케줄러를 사용한 동시성과 병렬 처리 (0) | 2020.05.29 |
6. 연산자 및 오류처리 (0) | 2020.05.28 |
5. 비동기 데이터 연산자와 변환 (0) | 2020.05.27 |