如何测试 RxSwift Observable.interval 进度

How to test RxSwift Observable.interval progress

我有一个视图模型,其行为由 Observable.interval 控制。本质上,它每隔 next 更新一个计时器标签,并在一段时间后更新另一个值。

修剪示例:

class WorkoutViewModel {
    private var _oneSecondTimer: Observable<Int> {
        return Observable<Int>.interval(1, scheduler: MainScheduler.instance)
    }
    private let _exerciseRemainingTime: Variable<Int> = Variable(20)

    func setupBehaviour() {
        _oneSecondTimer.subscribeNext() { [unowned self] _ in
            self._exerciseRemainingTime.value -= 1
            if self._exerciseRemainingTime.value == 0 {
                self.progressToNextExercise()
            }
        }
    }
}

我想对此进行测试,观察_exerciseRemainingTime的事件时间和值。

有没有办法使用 TestScheduler 模拟 _oneSecondTimer 滴答作响的虚拟时间?

是的,看看这个具有您正在寻找的行为的简单测试:https://github.com/ReactiveX/RxSwift/blob/b3f4bf1/Tests/RxSwiftTests/Tests/Observable+TimeTest.swift#L496

要将 TestScheduler 替换为 MainScheduler,我建议您将其作为依赖项注入。

此外,要检查 _exerciseRemainingTime 的值,您需要删除 private。但是,我不建议测试 class 的内部结构。删除 private 表示您是。相反,如果您要注入一个负责执行 progressToNextExercise 的对象,那么您可以测试它是否收到了进行下一个练习的调用。您只需在测试期间传递该对象的测试版本,就像您为调度程序使用 TestScheduler 一样。这样就不需要制作 _exerciseRemainingTime public 来测试它,甚至不需要知道它。

但是,为了这个问题的主要目的,忽略 _exerciseRemainingTime 的可见性,这就是我对调度程序的意思:

WorkoutViewModel.swift

class WorkoutViewModel {
    private var _oneSecondTimer: Observable<Int> {
        return Observable<Int>.interval(1, scheduler: scheduler)
    }

    // not `private` anymore.  also, a computed property
    var _exerciseRemainingTime: Observable<Int> {
        return self._oneSecondTimer.map { i in
            20 - i
        }
    }

    // injected via `init`
    private let scheduler: SchedulerType

    init(scheduler: SchedulerType) {
        self.scheduler = scheduler
    }
}

WorkoutViewModelTest.swift

func testExerciseRemainingTime() {
    let scheduler = TestScheduler(initialClock: 0)

    let res = scheduler.start(0, subscribed: 0, disposed: 23) {
        WorkoutViewModel(scheduler: scheduler)._exerciseRemainingTime
    }

    let correct = [
        next(1, 20), // output .Next(20) at 1 second mark
        next(2, 19), // output .Next(19) at 2 second mark
        next(3, 18),
        next(4, 17),
        next(5, 16),
        next(6, 15),
        next(7, 14),
        next(8, 13),
        next(9, 12),
        next(10, 11),
        next(11, 10),
        next(12, 9),
        next(13, 8),
        next(14, 7),
        next(15, 6),
        next(16, 5),
        next(17, 4),
        next(18, 3),
        next(19, 2),
        next(20, 1),
        next(21, 0),
        next(22, -1),
    ]

    XCTAssertEqual(res.events, correct)
}

需要考虑的几个注意事项:

为了让测试调度程序订阅和处理,我从视图模型中删除了 subscribeNext。我认为这无论如何都会改进它,因为您应该订阅视图控制器并且只使用视图模型为您提供 Observable。这避免了视图模型需要一个处理包和管理 Disposables 的生命周期。

您真的应该考虑公开 "internal" 少于 _exerciseRemainingTime 的内容。可能类似于 currentExercise: Observable<ExerciseEnum>,它在内部基于 _exerciseRemainingTime。这样,您的视图控制器就可以订阅并执行简单的视图控制器相关工作,继续下一个练习。

此外,为了简化测试,您可以将 20 变量注入视图模型,这样在测试中您可以提供更小的变量,例如 3 然后 correct 将只需要几个元素长。