Test Scheduler
โฐ TestScheduler ์ฌํ ๊ฐ์ด๋ โ ๊ฐ์ ์๊ฐ ์์ ์ ๋ณต
โ์ฝ๋๋ฅผ ๋๋ฆฌ๊ฒ ๋ง๋ค์ง ๋ง๊ณ , ์๊ฐ์ ๋น ๋ฅด๊ฒ ๋๋ ค๋ผ.โ
TestScheduler
๋ RxTest๊ฐ ์ ๊ณตํ๋ ๊ฐ์ ์๊ณ๋ก, ๋ฐ๋ฆฌ์ด ๋จ์ ๋น๋๊ธฐ ๋ก์ง๋ ์ฆ์ ๊ฒ์ฆํ ์ ์๊ฒ ํด ์ค๋๋ค. ์ด ๋ฌธ์๋ APIยทํจํดยท๋๋ฒ๊น ๋ ธํ์ฐ๋ฅผ ํ๊ธ๋ก ์์ธํ ์ค๋ช ํฉ๋๋ค.
1๏ธโฃ ๊ธฐ๋ณธ ๊ฐ๋
๋ค์ ๋ณด๊ธฐ
Tick(ํฑ)
TestScheduler๊ฐ ์ ์ํ ๊ฐ์ ์๊ฐ ๋จ์. ๋ณดํต 1ํฑ=10ms ๋ก ๊ฐ์ ํ์ง๋ง, ์ค์ ๋จ์๋ ๊ฐ๋ฐ์ ์์ .
Clock
ํ์ฌ ๊ฐ์ ์๊ฐ(์ ์). advanceTo(50)
โ 50ํฑ์ผ๋ก ๋ฐ๋ก ์ด๋.
Recorded
(time: Int, event: Event<Element>)
๊ตฌ์กฐ. ํ
์คํธ ๊ฒฐ๊ณผยท์
๋ ฅ ๋ชจ๋ Recorded ๋ฐฐ์ด๋ก ํํ.
2๏ธโฃ ํ์ ๋ฉ์๋ ํ๋์ ์ ๋ฆฌ
createHotObservable(_:)
ํ ์คํธ ์ ๋ ฅ ์ ์(๊ตฌ๋ ์ ๋ถํฐ ์ด๋ฒคํธ ์์ฝ)
.next(10, "a")
createColdObservable(_:)
๊ตฌ๋ ์ 0ํฑ๋ถํฐ ์นด์ดํธ
.next(5, 1)
start(created:subscribed:disposed:)
ํธ์ ๋ฉ์๋. Observable ๋ง๋ค๊ณ 200ํฑ๊น์ง ์คํํด ๊ฒฐ๊ณผ ๋ฐํ
scheduler.start { myStream }
advanceTo(_:)
ํด๋ญ์ ํน์ ์๊ฐ์ผ๋ก ์ ํ
scheduler.advanceTo(150)
createObserver(Element.self)
๋น Recorder. ์คํธ๋ฆผ์ ์๋์ผ๋ก ๊ตฌ๋ ํ ๊ฒฐ๊ณผ ์์ง
let res = scheduler.createObserver(String.self)
scheduleAt(_:action:)
ํน์ ์์ ์ ์ฝ๋ ์คํ
scheduler.scheduleAt(50) { subject.onNext(1) }
3๏ธโฃ ๋จ๊ณ๋ณ ์ปค์คํ
ํ
์คํธ ํ
ํ๋ฆฟ
func testCustomOperator() {
// 1) ๊ฐ์ ์๊ณ & Observer
let scheduler = TestScheduler(initialClock: 0)
let observer = scheduler.createObserver(Int.self)
// 2) ์
๋ ฅ ์ ์ (Cold)
let numbers = scheduler.createColdObservable([
.next(5, 1), .next(15, 2), .next(25, 3), .completed(35)
])
// 3) ์์คํ
์ธ๋ํ
์คํธ(SUT)
let sut = numbers.scan(0, +) // ๋์ ํฉ
// 4) ๊ตฌ๋
์ค์ผ์ค๋ง
scheduler.scheduleAt(0) {
sut.bind(to: observer).disposed(by: DisposeBag())
}
// 5) ๊ฐ์ ์๊ณ ์คํ (0~100ํฑ)
scheduler.start()
// 6) ๊ธฐ๋๊ฐ
let expected = [
.next(5, 1), .next(15, 3), .next(25, 6), .completed(35)
]
XCTAssertEqual(observer.events, expected)
}
4๏ธโฃ advanceTo vs start ์ฐจ์ด
ํน์ง
start
advanceTo
+ ์๋ ๊ตฌ๋
๊ตฌ๋ ์์
์๋ (subscribed:
ํ๋ผ๋ฏธํฐ, ๊ธฐ๋ณธ 200)
์ง์ scheduleAt
์ผ๋ก ์ง์
๋์คํฌ์ฆ
์๋ (disposed:
ํ๋ผ๋ฏธํฐ, ๊ธฐ๋ณธ 1000)
์๋ dispose ๊ฐ๋ฅ
์ฌ์ฉ ๋์ด๋
๊ฐํธ(์๋ผ์ธ)
์ ์ฐ(๋ณต์ก ์๋๋ฆฌ์ค)
๋๊ท๋ชจ ์๋๋ฆฌ์คยท์ฌ๋ฌ ๋จ๊ณ ๊ตฌ๋ /ํด์ ๋ฅผ ํ ์คํธํ ๋ advanceTo ๋ฐฉ์์ ๊ถ์ฅํฉ๋๋ค.
5๏ธโฃ ์ค์ : ์ง์ ๋ฐฑ์คํ ์ฌ์๋ ํ
์คํธ
func testExponentialBackoff() {
enum MyErr: Error { case fail }
let scheduler = TestScheduler(initialClock: 0)
let source = scheduler.createColdObservable([
.error(10, MyErr.fail)
])
// 1,2,4ํฑ ์ง์ฐ ํ ์ฌ์๋ (์ด 4ํ)
let backoff = source.retryWhen { errors in
errors.enumerated().flatMap { attempt, error -> Observable<Int> in
guard attempt < 3 else { return Observable.error(error) }
let delay = Int(pow(2.0, Double(attempt)))
return Observable<Int>.timer(.seconds(delay), scheduler: scheduler)
}
}
let res = scheduler.start(created: 0, subscribed: 0, disposed: 50) { backoff }
// 0+10=10 error
// retry1 delay1 โ 11 error(21)
// retry2 delay2 โ 23 error(33)
// retry3 delay4 โ 37 error(47)
XCTAssertEqual(res.events.last?.time, 47)
}
6๏ธโฃ ๋๋ฒ๊ทธ ํ ๐ ๏ธ
ํด๋ญ ๋ก๊น :
print(scheduler.clock)
์ค๊ฐ ํ์ธRecorded ๋ฐฐ์ด ์ถ๋ ฅ :
observer.events.forEach(print)
๋ก ์์ ์ฒดํฌ๋ถํ์ํ ๋ง๋ธ ์ค๋ณต ์ ๊ฑฐ : ๋ฐ๋ณต๋๋ ํจํด์
generateRecorded
ํฌํผ ํจ์ ์ฌ์ฉ
7๏ธโฃ ์ฒดํฌ๋ฆฌ์คํธ โ
Cold/Hot ์ ํ ์ดํด
๊ตฌ๋ ์์ ์ด ๊ฒฐ๊ณผ์ ๋ฏธ์น๋ ์ํฅ ํ์
์๊ฐ ๋จ์ ์ผ๊ด์ฑ
1ํฑ=10ms ๋ฑ ํ๋ก์ ํธ ๊ณตํต ๊ท์น
start ํ๋ผ๋ฏธํฐ ์์
created
, subscribed
, disposed
ํ์์ ๋ง๊ฒ ์กฐ์
Equatable ๊ตฌํ
์ปค์คํ ๋ชจ๋ธ ๋น๊ต ์ ํ๋ ํ๋ณด
๋จ์ผ ์ฑ ์ ํ ์คํธ
ํ๋์ ์ฐ์ฐ์/๊ฒฝ๋ก๋ง ๊ฒ์ฆํด ์คํจ ์์ธ ๋ช ํํ
8๏ธโฃ Mini Quiz
createObserver
๋ก ์์งํ Recorded ์ด๋ฒคํธ๋ฅผscheduler.advanceTo(100)
์ ์ ์กฐํํ๋ฉด ์ด๋ค ๊ฐ์ด ์๋์?start(subscribed: 50)
๋ก ์ค์ ํ๋ฉด.next(10, x)
Cold ์ด๋ฒคํธ๋ ๋ช ํฑ์ ๋ฐ์ํ๋์?Hot Observable ํ ์คํธ์์
scheduleAt(0)
๊ณผscheduleAt(100)
๊ตฌ๋ ์ ๊ฒฐ๊ณผ ์ฐจ์ด๋?
์ด์ TestScheduler๊น์ง ๋ง์คํฐํ์ต๋๋ค! ์ค์ ํ๋ก์ ํธ์ ๋น๋๊ธฐ ์ฝ๋๋ฅผ ์์ ์๊ฒ ํ ์คํธํด ๋ณด์ธ์. ๐
Last updated