[iOS] HighwayInfo: RxTest로 유닛테스트하기

zoe·2023년 6월 1일
0
post-thumbnail

안녕하세요, 이번 포스트는 RxSwift 코드 테스트에 대한 이야기 입니다.
앱을 MVVM으로 구성했더니 테스트에 용이한 구조가되었습니다. MVVM 아키텍처의 또 다른 이점은 코드의 테스트 용이성 증가라고 할 수 있는데요, 오늘은 특히 뷰 모델에 대한 단위 테스트를 만든 경험을 공유하고자 합니다.

RxTest 목적

  • 모듈이 정해진 기능을 정확히 수행하는지 확인하기 위해
  • 프로그램의 각 부분을 고립시켜 각각의 부분이 의도된대로 정확히 동작하는지 확인하기 위해

RxTest 라이브러리를 이용하여 RxSwfit 연산자 및 코드에 대한 테스트를 작성해 보았습니다.

테스트 대상 결정

테스트를 하기 전 실제로 테스트하고 싶은 것이 무엇인지 생각해보았습니다. 뷰 처럼 테스트를 할 수 없는 부분을 제외한 비즈니스 로직을 테스트 하기로 결정했습니다.
특히 뷰 모델의 Input에 대해서 원하는 Output이 잘 나오는지 확인하는것을 목표로 했습니다.

테스트를 위한 준비

테스트를 위해 필요한 프로퍼티를 정의해줍니다.

private var viewModel: SearchViewModel!
private var disposeBag: DisposeBag!
private var scheduler: TestScheduler!
private var input: SearchViewModel.Input!
private var output: SearchViewModel.Output! 

저는 뷰 모델을 테스트 할 것이기 때문에 뷰 모델과 인풋, 아웃풋, 스케쥴러 등을 선언했습니다.

override func setUpWithError() throws {
    viewModel = SearchViewModel(useCase: MockSearchUseCase(), coordinator: nil)
    scheduler = TestScheduler(initialClock: 0)
    disposeBag = DisposeBag()
}

setUp() 에서 각 테스트가 시작되기 전에 호출되는 프로퍼티들을 초기화 해줍니다.

  • scheduler는 테스트 스케쥴러가 각 테스트에서 사용할 인스턴스 입니다.
  • RxTest에 의해 내부적으로 계산된 가상 시간 단위를 사용합니다.
override func tearDownWithError() throws {
    viewModel = nil
    disposeBag = nil
}

tearDown() 에서 뷰모델과 디즈포즈백의 메모리를 해제해줍니다.

테스트 코드 작성

  1. 옵저버를 만듭니다
  2. 테스트 값을 추가합니다.
  3. 실제 결과가 예상 결과와 일치하는지 확인합니다.
func test_fetch_search_result() {
    let searchResultObserver = scheduler.createObserver([LocationInfo].self)
    let viewWillAppearTestableObservable = scheduler.createHotObservable([.next(10, ())])
    let keywordObservable = scheduler.createHotObservable([.next(20, "seoul")])

    input = SearchViewModel.Input(viewWillAppear: viewWillAppearTestableObservable.asObservable(),
                                  searchKeyword: keywordObservable.asObservable(),
                                  itemSelected: Observable.just(nil))
    output = viewModel.transform(input: input)
    output.searchResult.subscribe(searchResultObserver).disposed(by: disposeBag)
    scheduler.start()

    XCTAssertEqual(searchResultObserver.events, [
        .next(20, [LocationInfo(
            uuid: "test_id",
            name: "newLocation",
            businessName: "eroom",
            distance: "10",
            coordx: "10",
            coordy: "10",
            address: nil
        )])
    ])
}

scheduler.start() 를 통해 스케줄러를 시작하고 옵저버가 이벤트를 수신합니다.
테스트를 빌드하고 실행한 후 성공여부를 확인했고, 예상한 결과와 일치하는 것 을 알 수 있었습니다.

사용하지 않는 코드 발견

테스트코드 작성의 이점은 코드를 다시 한번 되돌아볼 수 있다는 점입니다.
실제로 아래 사진에서 SearchViewModel에 대한 테스트 진행 중 더 이상 쓰이지 않는 코드를 발견해서 리팩토링 할 수 있었습니다.

코드 커버리지


엑스코드 테스트에서 제공하는 코드 커버리지 확인 기능을 통해
커버가 안 된 부분을 확인하고

새로운 테스트를 추가 작성했습니다.


화면 전환 처럼 테스트 코드로 확인할 수 없는 부분을 제외한 모든 코드에 대해 테스트를 마쳤습니다.

마무리

이렇게 유닛 테스트를 통해 개발한것이 제대로 작동하고 있는지 확인해보았습니다.
비즈니스 로직에서 뷰 라이프 사이클을 분리하였더니 뷰 모델을 매우 간단하게 테스트 할 수 있었습니다.
RxTest도 처음이고 단위테스트를 하는것도 쉽지 않았지만, 유닛테스트 작성을 통해 사용자가 버그에 최소한으로 노출될 수 있도록 한 점이 뿌듯하네요:)

profile
개발하면서 마주친 문제들을 정리하는 공간입니다.

0개의 댓글