MINRYUL
류링류링
MINRYUL
전체 방문자
오늘
어제
  • 분류 전체보기
    • Swift
      • 학습
    • iOS
      • Toy-Project
      • 학습
      • Metal
    • CS
    • TIL

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • Protocol
  • Swift
  • METAL
  • WWDC 2024
  • CollectionView Cell
  • TDD
  • dynamic frameworkm
  • RxTest
  • RxSwift
  • opaque type
  • BDD
  • Existential type
  • Clean Architecture
  • Custom Calendar
  • RxCocoa
  • AttributeText
  • static framework
  • ViewStore
  • Any
  • RxNimble
  • configuration_bulid_dir
  • some
  • ios
  • Existential any
  • TableView Cell
  • collectionView
  • demangle
  • urlsession
  • WWDC
  • tuist

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
MINRYUL
iOS/학습

RxNimble + Quick Unit Test

iOS/학습

RxNimble + Quick Unit Test

2022. 5. 26. 19:55

Quick

  • BDD(Behavior-Driven Development)를 위한 프레임워크
  • 각 클로져 단위 마다 descirption을 묶어서 사용하기 때문에 가독성이 좋음
  • Given(준비) - When(실행) - then(검증)
    • descibe(Given) → context(When) → it(Then) 순으로 구성
    • beforeEach 초기화 블록을 통해 it 이 실행 되기 이전의 클로져에 있는 beforeEach 블록들을 순차적으로 실행함
    • afterEach 를 통해 it 블록이 실행 된 이후 해야할 초기화 작업을 할 수 있음.
  • beforeEach 를 통해 각 it 테스트는 ‘순수한' 상태에서 시작하는 것이 좋음
    • 각 it를 테스트 하고 ViewModel의 초기화, 동작이 진행된 코드를 다시 사용해서 테스트 하는 것은 좋지 않다는 뜻.
      • 따라서 context 클로져 내부에 context, describe 등이 추가될 수 있음. (동작을 연결 시킨다.)
    • ‘동작', ‘초기화' 작업은 beforeEach를 통해서 하자.
    • afterEach에서는 메모리 초기화.

 

Quick 예제

import Quick

class ViewModelTest: QuickSpec {
  override func spec() {
    //모든 테스트 코드가 spec()을 override 한 함수 내에서 작성됨.
    describe("어떤 화면이 로드되고") {
		beforeEach {
			//...
		}
		afterEach {
			//...
		}

		context("버튼을 누를 경우") {
        	beforeEach {
				//...
			}

        	it("특정 값이 된다.") {
            	//...
       		}
            
      	}
    }
  }
}

Nimble

  • XCTAssert 보다 Test 코드를 간단하게 작성할 수 있게 나온 프레임워크
  • XCTAssert는 직접 테스트 코드를 작성할 때 실패 메세지를 추가해줘야한다. 그에 반해 Nimble은 실패 메세지를 읽기 쉽게 자동 작성해줌.
  • 정말 다양한 assertion 제공
  • https://github.com/Quick/Nimble (참고)

Nimble + Quick 예제

//https://github.com/Quick/Quick/blob/main/Documentation/ko-kr/QuickExamplesAndGroups.md
import Quick
import Nimble

class DolphinSpec: QuickSpec {
  override func spec() {
    describe("a dolphin") {
      var dolphin: Dolphin?
      beforeEach {
        dolphin = Dolphin()
      }

      describe("its click") {
        var click: Click?
        beforeEach {
          click = dolphin!.click()
        }

        it("is loud") {
          expect(click!.isLoud).to(beTruthy())
        }

        it("has a high frequency") {
          expect(click!.hasHighFrequency).to(beTruthy())
        }
      }
    }
  }
}

 

RxNimble

  • RxBlocking, RxTest, Nimble에 의존성이 있다.
expect(self.output.value)
 .events(scheduler: scheduler, disposeBag: disposeBag)
  .to(equal(
   [
	.next(0, 0),		
	...
	...
   ]
  ))
  • Nimble의 expect().to() 에서 expect().events().to()로 events를 체이닝 할 수 있다.
  • scheduler에 등록된 가상의 시간과 이벤트에 맞춰 self.output.value의 스트림에 가상시간과 값을 함께 비교할 수 있다.
    • TestScheduler 라는 RxTest 라이브러리의 객체가 가상시간을 구현한다.
    • 이때 scheduler는 TestScheduler

 

RxNimble + Quick ViewModel Test 예제

import Foundation

import RxSwift
import RxCocoa

struct TestInput {
  var inputTitle: AnyObserver<String?>
  var upButton: AnyObserver<Void?>
  var downButton: AnyObserver<Void?>
}

struct TestOutput {
  var title: Driver<String>
  var number: Driver<Int>
}

struct TestViewModel {
  let testInput: TestInput
  let testOutput: TestOutput
  
  private var disposedBag: DisposeBag = DisposeBag()
  
  //MARK: - Input
  private let _inputTitle = BehaviorSubject<String?>(value: nil)
  private let _upButton = BehaviorSubject<Void?>(value: nil)
  private let _downButton = BehaviorSubject<Void?>(value: nil)
  
  //MARK: - Output
  private let _title = BehaviorSubject<String>(value: "")
  private let _number = BehaviorRelay<Int>(value: 0)
  
  init() {
    testInput = TestInput(
      inputTitle: _inputTitle.asObserver(),
      upButton: _upButton.asObserver(),
      downButton: _downButton.asObserver()
    )
    
    testOutput = TestOutput(
      title: _title.asDriver(onErrorJustReturn: ""),
      number: _number.asDriver(onErrorJustReturn: 0)
    )
    
    self._bindInputTitle()
    self._bindUpButton()
    self._bindDownButton()
  }
  
  private func _bindInputTitle() {
    self._inputTitle
      .compactMap { $0 }
      .bind(to: _title)
      .disposed(by: disposedBag)
  }
  
  private func _bindUpButton() {
    self._upButton
      .compactMap { $0 }
      .map { _ -> Int in
        let number = _number.value
        return number + 1
      }
      .bind(to: _number)
      .disposed(by: disposedBag)
  }
  
  private func _bindDownButton() {
    self._downButton
      .compactMap { $0 }
      .map { _ -> Int in
        let number = _number.value
        return number - 1
      }
      .bind(to: _number)
      .disposed(by: disposedBag)
  }
}
import Foundation

import RxSwift
import RxTest
import Nimble
import RxNimble
import Quick

class RxNimbleTest: QuickSpec {
  override func spec() {
    var scheduler: TestScheduler!
    var dispoedBag: DisposeBag!
    
    describe("어떤 화면이 로드 되고") {
      var viewModel: TestViewModel!
      
      beforeEach {
        scheduler = TestScheduler(initialClock: 0)
        dispoedBag = DisposeBag()
        viewModel = TestViewModel()
      }

      afterEach {
        scheduler = nil
        dispoedBag = nil
        viewModel = nil
      }
      
      context("타이틀이 입력 되면") {
        beforeEach {
          scheduler.createColdObservable([
            .next(5, "테스트 타이틀")
          ])
          .bind(to: viewModel.testInput.inputTitle)
          .disposed(by: dispoedBag)
        }
        
        it("타이틀이 테스트 타이틀로 변경 된다.") {
          expect(viewModel.testOutput.title.compactMap { $0 })
          .events(scheduler: scheduler, disposeBag: dispoedBag)
          .to(equal([
            .next(0, ""),
            .next(5, "테스트 타이틀")
          ]))
        }
      }
      
      context("업 버튼이 3번 터치 되면") {
        beforeEach {
          scheduler.createColdObservable([
            .next(5, Void()),
            .next(10, Void()),
            .next(15, Void())
          ])
          .bind(to: viewModel.testInput.upButton)
          .disposed(by: dispoedBag)
        }
        
        it("숫자가 3이 된다.") {
          expect(viewModel.testOutput.number)
          .events(scheduler: scheduler, disposeBag: dispoedBag)
          .to(equal([
            .next(0, 0),
            .next(5, 1),
            .next(10, 2),
            .next(15, 3)
          ]))
        }
      }
      
      context("다운 버튼이 2번 터치 되면") {
        beforeEach {
          scheduler.createColdObservable([
            .next(5, Void()),
            .next(10, Void())
          ])
          .bind(to: viewModel.testInput.downButton)
          .disposed(by: dispoedBag)
        }
        
        it("숫자가 -2이 된다.") {
          expect(viewModel.testOutput.number)
          .events(scheduler: scheduler, disposeBag: dispoedBag)
          .to(equal([
            .next(0, 0),
            .next(5, -1),
            .next(10, -2)
          ]))
        }
        
        context("업 버튼이 1번 터치 되면") {
          beforeEach {
            scheduler.createColdObservable([
              .next(15, Void())
            ])
            .bind(to: viewModel.testInput.upButton)
            .disposed(by: dispoedBag)
          }
          
          it("숫자가 -1이 된다.") {
            expect(viewModel.testOutput.number)
              .events(scheduler: scheduler, disposeBag: dispoedBag)
              .to(equal([
                .next(0, 0),
                .next(5, -1),
                .next(10, -2),
                .next(15, -1)
              ]))
          }
        }
      }
    }
  }
}

'iOS > 학습' 카테고리의 다른 글

Storyboard 에서 Generic Type 의존성 주입하기 2  (0) 2022.08.29
Custom Horizontal FlowLayout  (0) 2022.06.21
iOS View 에서의 init 함수들  (0) 2022.05.08
iOS RxDataSource로 Custom Xib CalendarView 만들기  (0) 2022.05.07
iOS에서의 Frame vs Bounds  (0) 2022.04.24
  • Quick
  • Quick 예제
  • Nimble
  • Nimble + Quick 예제
  • RxNimble
  • RxNimble + Quick ViewModel Test 예제
'iOS/학습' 카테고리의 다른 글
  • Storyboard 에서 Generic Type 의존성 주입하기 2
  • Custom Horizontal FlowLayout
  • iOS View 에서의 init 함수들
  • iOS RxDataSource로 Custom Xib CalendarView 만들기
MINRYUL
MINRYUL
열심히 살자

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.