프로젝트 진행 중 테스트 코드를 작성하다가 다음과 같은 코드가 잘 실행되는 것을 보게 되었다.
var scheduler: TestScheduler!
var viewModel: SomeViewModel!
// 예시를 위한 가짜 코드
func test_SomeClass_someMethod_expectedResult() {
// ...
let observable = scheduler.createColdObservable(...)
subscription = observable.bind(onNext: { [weak self] index in
self?.viewModel.changeState(at: index)
})
scheduler.start()
let result = viewModel.indices.value // viewModel.indices -> BehaviorRelay<[Int]>
// ...
}
마지막 줄의 result
의 값을 예상할 수 없다. (scheduler이 비동기로 작동한다고 생각했기 때문)
scheduler.start()
를 하면 TestScheduler
가 시작된다.TestScheduler
가 observable
의 recordedEvent들을 "비동기"로 방출시킨다.subscription
에서 bind(onNext:)
에 등록한 클로저가 이벤트 방출 시 마다 실행된다.let result = viewModel.indices.value
줄에서 result의 값이 바뀌었을 수도, 안바뀌었을 수도 있다.result
의 값이 항상 동일하다.
scheduler이 비동기로 작동한다면 result
의 값이 항상 동일하게 나올 수 없다.
let result = ...
이 실행될 수 있기 때문동일하게 나온다는 것은 scheduler가 동기로 작동한다는 뜻
그래서 TestSchduler.start()
메서드를 살펴보았다.
중간의 guard let next = self.findNext() else ...
구문에 중단점을 잡고 테스트 코드를 디버깅해보았다.
observable
의 recordedEvent에서의 time
과 next.time
이 일치하는 것을 확인하였고,
next.invoke()
가 호출되면 bind(onNext:)
의 클로저가 호출된다는 것을 확인하였다.
또한 repeat-while
문을 한 번 들어오면 self.findNext()
가 nil
을 반환할 때 까지
해당 반복문을 빠져나오지 않는다는 것을 알 수 있었고, 결론적으로 scheduler
가 동기로 작동한다는 것을 확인하였다.
scheduler.start()
는 scheduler에 등록되어 있는 recordedEvent 리스트를
그 이벤트의 Virtual Time에 맞게 순서대로 실행한다.
이 때 내부적으로 repeat-while
문을 통해 recordedEvent의 이벤트를 방출시키고
이벤트가 종료될 때 까지 해당 반복문을 빠져나오지 않기 때문에
let result = viewModel.indices.value
에 항상 동일한 값이 들어갈 수 있었던 것이다.