RxSwift 单元测试基础
本文最后更新于 2021年4月4日 晚上
公司在新项目中准备实施敏捷方法, 而在项目中准备使用 RxSwift, 故探索了一些 RxSwift 的单元测试方法, 记录在这篇文章中.
为什么大部分项目不写单元测试?
- 写的代码没有 bug.
- 有专业测试人员, 为什么还要写单元测试?
- 写单元测试不好玩.
- 写单元测试会拖延项目前期推进速度.
为什么要写单元测试?
- 为了减少 bug.
- 写单元测试和有没有专门测试人员没有太大关系, 因为两者的 scope 不同.
- 为了增加编程乐趣.
- 有富余时间可以写单元测试(可能是少部分核心功能的单元测试).
为什么看这篇文章? 因为想写 RxSwift 客户代码的单元测试.
既然本文标题是 “RxSwift 单元测试基础”, 那肯定就要拿单元测试说点事儿. RxSwift 提供有两个互补的单元测试工具: RxTest
和 RxBlocking
, 下面主要围绕这两个工具进行讲解.
针对流测试时候的难点
相比于测试传统的命令式代码, 响应式代码测试时有如下两个难点:
- 测试的对象是流中的事件.
- 事件是随时间改变而添加到流上的, 需要一种机制来”记录”这些事件的发生, 从而进行测试.
测什么?
根据 APP 的需求, 并结合单元测试的特点, 需要测试:
- View 的控制代码部分(即除去界面的实现)
- ViewModel
- Model
故一般流程是:
- 明确需求
- 列出待测单元列表
- 写测试
RxTest
提供对流生成过程的精确控制(TestScheduler
提供的能力). 因为在 RxSwift 中, 通过 Scheduler 来抽象和描述任务的执行方式, 以及调度任务执行结果(即发射的事件).
通过 TestScheduler
, 可以创建模拟的 Observable
或 Observer
, 且记录它们上面事件发生时间, 详见官方文档.
用法是准备 TestScheduler
和一个 Disposable
持有每个测试中的订阅, 如下所示:
1 |
|
需要注意的是其中的时间是虚拟时间, 在 TestScheduler
的构造方法中还可以指定 resolution
参数, 用以表示虚拟时间的精度(默认是 1).
RxBlocking
允许用户将当前线程阻塞(使用 toBlocking()
)以获取一段时间内的所有事件, 这样在最后可以进行同步测试. 它适用于有限序列, 即会出现终止事件的序列.
如下三个是最常用的操作符:
toArray
first
last
RxBlocking
的使用非常简单, 因为封装了许多概念, 用户使用简单的接口即可进行测试. 但有如下缺点:
- 只能测试有限序列.
- 由于它的工作原理是阻塞当前线程并锁定 run loop, 因此如果被测 Observable 使用时间操作符调度(比如延迟 3 秒)事件, 则实际测试过程中也会等这么长的时间.
- 无法对事件产生时候的时间戳进行测试(因为它无法记录事件产生时间)
- 不适用于需要异步输入流的测试, 比如一个流需要另外一个流去触发(trigger).
使用 RxBlocking
可以让测试更简单. RxTest
则提供对于流生成的精确控制.
参考
- “Reactive Programming with Swift” PDF
- https://www.raywenderlich.com/7408-testing-your-rxswift-code