본문 바로가기
iOS/개념

Test 코드 작성하기 : UnitTest, Nimble, RxTest, RxBlocking

by 나리._. 2022. 2. 15.

개발 과정에서 기능 개발 말고도 중요한 과정이 있다.

바로 개발한 것이 제대로 작동되는지 검증하는 과정이다.

 

당연한 코드가 당연하게 동작하는 지 확인하는 것.

이것이 꼭 필요할까? 

그냥 기능을 구현하는데에 더 시간을 쏟는 게 낫다고 생각할 수도 있지만

이렇게 테스트 코드를 작성하고 확인하는 것이 장기적으로 생각했을때 기회비용을 최소화 하는 방법이라고 한다. (납득.)

 

개발자가 발생 가능한 모든 가정을 예측할 수 없기 때문에 우리는 이러한 테스트 코드를 작성하여 체크를 한다.

예를 들어, Int형 파라미터를 받는 함수에 Int타입이 연산하는 범위를 넘어선 숫자가 들어오거나 다른 타입의 파라미터가 들어온다 등등

경우 버그가 나타날 수 있다.

이때 우리가 테스트 코드를 작성했다면? 사용자가 버그에 최소한으로 노출되도록 할 수 있다는 것.

 

UnitTest?

모듈 전체( 하나) 통째로 검증하는 것이 아니라, 단위별로 테스트 하기 때문에 UnitTest라고 말한다.

 

그럼 이러한 테스트 코드를 어떻게 작성해야 할까?

Xcode에는 Xcode 프로젝트에 대한 단위테스트, 성능 테스트, UI 테스트를 가능하게 하는 프레임워크가 존재한다

-> 그것이 바로 XCTest

 

XCTestCase는 테스트 케이스, 테스트 방법, 성능 테스트를 정의하기 위한 기본 클래스이고

XCTestCase를 상속하는 클래스를 통해서 프로젝트 내의 모델, 모델 메서드 등을 테스트할 수 있다.

클래스를 통해 테스트 실행 전 초기 상태 준비(setUp 함수)부터  테스트, 테스트 완료된 정리까지(tearDown함수) 완료할 수 있다.

 

그럼 Nimble은 무엇일까?

Nimble은 테스트를 위해 기본 테스트 프레임워크인 XCTestCase를 이용해도 되지만 조금 더 편리하고 읽기 쉬운 오픈소스 프레임워크이다.

// XCTestCase 이용
XCTAssertEqul(table.rowCount, 0, "row의 개수는 0이어야함")
// -> Nimble 이용 (직관적 표현)
expect(table.rowCount).to(equal(0), description: "row의 개수는 0이어야함")

 

이렇게 값을 확인하는 테스트를 진행할 수 있다.

하지만 RxSwift의 기반의 코드들은 값이 아닌 이벤트의 흐름 (시퀀스)이다.

 

RxSwift의 기반의 코드에서 정상적으로 이벤트가 발생하는지 확인하려면 어떻게 해야 할까?

subscribe 없이 이벤트를 동기적으로 받는 방법이 있을까?

기대하는 시점(버튼을 탭 하거나.. 등)에 이벤트가 잘 발생하는지 확인하는 방법이 있을까?

 

이러한 RxSwift 하에서 테스트를 하기 위해서는 어떻게 해야 할까?

RxTest, RxBlocking 프레임워크를 이용하여 테스트를 하면 된다!

 

RxTest

RxTest는 Observable에 가상의 시간 개념을 주입해서 테스트하는 방식이다.

이 시간 개념을 주입함으로써 언제? 무엇이? 나왔는지 검증이 가능하다!

임의의 Observer를 통해서 가상의 시간이 흐를 때까지 관찰한 후 타이밍과 이벤트를 반환을 한다.

 

RxTest 예시

// 가상의 시간 흐름
let scheduler = TestScheduler(initialClock:0)
// 원하는 시점에 Event가 발생하는 Observable
let hotObservalue = scheduler.createHotObservable( [
        .next(1, "A")
        .next(2, "B")
        .next(3, "C")
])
// 원하는 시점에 Event가 발생하는 Observable
let coldObservalue = scheduler.createColdObservable( [
        .next(1, "A")
        .next(2, "B")
        .next(3, "C")
])
// Hot observalue 관찰할 임의의 observer(관찰자)
let observer = scheduler.createObserver(String.self)

hotObservable
    .subscribe(observer)
    .disposed(by: disposeBag)

// 가상의 시간 start
scheduler.start()

// nimble 이용하여 예상과 같은 데이터가 나왔는지 확인
expect(observer.events).to (
    equal([
        .next(1, "A")
        .next(2, "B")
        .next(3, "C")
    ])
)

원하는 시점에 Event가 발생하는 Observable이 두 개가 있는데

하나는 scheduler.createHotObservable

또 다른 하나는 scheduler.createColdObservable이다.

 

이 두 개의 차이점은 무엇일까?

HotObservable은 구독의 여부와 관계없이 이벤트가 발생하는 것이고

ColdObservable은 구독이 되어야 순서대로 이벤트가 발생하는 것이다.

 

위의 코드에서는 HotObservable을 통해 구독과 상관없이

가상의 시간이 흐를 때까지 관찰하고

위의 코드를 앞서 말했던Nimble을이용하여 어떠한 시간에 어떠한 값이 나왔는지를 조합하여 배열값으로 전환하여 예상과 같은 데이터가 나왔는지 확인할 수 있다.

 

 

이제 RxTest 무엇인지 이해했으니 RxBlocking 무엇인지 알아보자!

 

RxBlocking

RxBlocking은  특정시간동안 방출된 Observable의 이벤트를 검증한다.

RxBlocking은 RxTest의 scheduler(가상의 시간 흐름)의 개념이 존재하지 않는다.

 

RxBlocking 예시

// BlockingObservable로 변경
let observable = Observable.of("A","B","C").toBlocking(timeout: 2)
// observable의 next이벤트를 Array로 전환 (해당 observable이 completed 될때까지)
let values = try! observable.toArray()
// 예상했던 값과 같은지 확인
expect(values).to(equal(["A","B","C")]))

RxBlocking에서는 RxBlocking에서 제공하는 BlockingObservable 객체를 이용한다.

여기서 해당 observable이 completed 될 때까지 라고 했는데 그럼 completed가 보장되지 않을 때는 어떻게 해야 할까?

위의 코드에서 toBlocking(timeout: 2)을 볼 수 있다.

이 코드를 통해서 completed가 되지않아도 해당 시간동안 발생한 이벤트에 대해서만 진행을 할 수 있게 된다.

 

이렇게 Rx 코드를 테스트할 수 있는 두 가지 프레임워크에 대해 알아보았는데

두 개의 차이는 스케쥴러의 유무인 것 같다.

스케쥴러의 개념이 필요하다거나 원하는 타이밍에 원하는 값이 나오는지 확인해야 할 경우에는  -> RxTest

그게 아니고 단순히 내가 기대하는 값과 어떠한 Observable 방출하는 값이 같은지만 확인하고 싶다면 RxBlocking 사용하면 좋을 것같다.