RxJS 为RxJS贡献测试
本文档涉及针对 RxJS 存储库内部的编写大理石测试,适用于希望帮助维护 RxJS 存储库的任何人。 相反,RxJS的用户应查看为应用编写大理石测试的指南。主要区别在于,在手动使用和使用 testScheduler.run(callback)帮助程序之间,TestScheduler 的行为有所不同。
“大理石测试”是使用称为的专用 VirtualScheduler 的测试 TestScheduler
。它们使我们能够以同步且可靠的方式测试异步操作。“大理石符号”已经被 @jhusain,@headinthebox,@mattpodwysocki 和 @andrestaltz 等人的许多教义和文档改编而成 。实际上,AndréStaltz 最初将其推荐为创建单元测试的 DSL,此后被更改和采用。
也可以看看
基本方法
单元测试中已添加了辅助方法,以使创建测试更加容易。
hot(marbles: string, values?: object, error?: any)
-创建一个“热”的可观察对象(主题),当测试开始时其行为就好像已经在“运行”。一个有趣的区别是,hot
大理石允许^
角色发出“零帧”位置的信号。这就是开始订阅要测试的可观察对象的时间。cold(marbles: string, values?: object, error?: any)
-创建一个“冷”可观察的对象,其可在测试开始时开始订阅。expectObservable(actual: Observable<T>).toBe(marbles: string, values?: object, error?: any)
-计划何时刷新 TestScheduler 的断言。TestScheduler 将在您的茉莉花it
块的结尾自动刷新。expectSubscriptions(actualSubscriptionLogs: SubscriptionLog[]).toBe(subscriptionMarbles: string)
-就像expectObservable
为 testScheduler 刷新的时间安排断言一样。双方cold()
并hot()
返回一个可观察与属性subscriptions
类型SubscriptionLog[]
。给subscriptions
作为参数传递给expectSubscriptions
断言它是否匹配subscriptionsMarbles
在给定的大理石图toBe()
。订阅大理石图与可观察大理石图略有不同。在下面阅读更多内容。
符合人体工程学的默认值 hot
和 cold
在 hot
和 cold
方法中,除非将 values
参数传递给方法,否则在大理石图中指定的值字符将作为字符串发出。为此:
hot('--a--b')
将发出"a"
,"b"
而
hot('--a--b', { a: 1, b: 2 })
将发出 1
和 2
。
同样,未指定的错误将默认为string "error"
,因此:
hot('---#')
将发出错误,"error"
而
hot('---#', null, new SpecialError('test'))` 会发出 `new SpecialError('test')
大理石语法
大理石语法是一个字符串,表示随着时间发生的事件。任何大理石弦的第一个字符
始终代表“零帧”。“帧”在某种程度上类似于虚拟毫秒。
"-"
时间:10个“帧”的时间流逝。"|"
complete:成功完成一个可观察的对象。这是可观察到的生产者信号complete()
"#"
错误:终止可观察值的错误。这是可观察到的生产者信号error()
"a"
任何字符:所有其他字符表示生产者信令发出的值next()
"()"
同步分组:当多个事件需要同步在同一帧中时,使用括号将这些事件分组。您可以通过这种方式将下一个值,完成或错误分组。初始位置(
确定了其值的发出时间。"^"
订阅点:(仅热观测值)显示测试的可观测物将订阅到该热观测值的点。这是可观察到的“零帧”,在之前的每一帧^
都会为负。
例子
'-'
或'------'
:等效于 Observable.never()
,或从不发出或完成的可观察物
|`: 相当于 `Observable.empty()
#`: 相当于 `Observable.throw()
'--a--'
:一个可观察的对象,等待 20个“帧”,发出值a
,然后永不完成。
'--a--b--|'`:在第20帧发射`a`,在第50帧发射`b`和在第80帧上,`complete
'--a--b--#'`:在第20帧发射`a`,在第50帧发射`b`和在第80帧上,`error
'-a-^-b--|'
:在热观测下,在第-20帧发射a
,然后在第 20帧发射b
,在第 50 帧上发射complete
。
'--(abc)-|'`:上框架20,发射`a`,`b`以及`c`,然后在框架80`complete
'-----(a|)'
:在第50帧上,发射a
和complete
。
订阅大理石语法
订阅大理石语法与常规大理石语法略有不同。它表示订阅,并且随着时间的推移发生取消订阅点。该图中不应显示其他类型的事件。
"-"
时间:段落的10个“帧”。"^"
订阅点:显示订阅发生的时间点。"!"
取消订阅点:显示取消订阅的时间点。
订购大理石图中,最多 应有一个^
点,并且最多 应有一个!
点。除此之外,该-
角色是订阅大理石图中唯一允许使用的角色。
例子
'-'
或'------'
:从未发生过订阅。
'--^--'
:订阅发生在 20 个“帧”的时间之后,并且该订阅并未取消订阅。
'--^--!-'
:在第 20 帧发生了订阅,而在第50帧未订阅。
测试的解剖
基本测试可能如下所示:
const e1 = hot('----a--^--b-------c--|');
const e2 = hot( '---d-^--e---------f-----|');
const expected = '---(be)----c-f-----|';
expectObservable(e1.merge(e2)).toBe(expected);
- 可观察对象的
^
字符hot
应始终对齐。 - 在第一个字符的
cold
观测值或预期的观测应始终被相互对准,并与^
热观测的。 - 如果可以,请使用默认发射值。指定
values
何时需要。
具有指定值的测试示例:
const values = {
a: 1,
b: 2,
c: 3,
d: 4,
x: 1 + 3, // a + c
y: 2 + 4, // b + d
}
const e1 = hot('---a---b---|', values);
const e2 = hot('-----c---d---|', values);
const expected = '-----x---y---|';
expectObservable(e1.zip(e2, function(x, y) { return x + y; }))
.toBe(expected, values);
- 使用相同的散列查找所有值,这可确保多次使用相同字符具有相同值。
- 对于它们代表的结果,使结果值尽可能地明显,毕竟这是测试,我们要的是清晰度而不是效率,因此
x: 1 + 3, // a + c
要好得多x: 4
。前者说明了为什么是 4,而后者则没有。
一个带有订阅断言的测试示例:
const x = cold( '--a---b---c--|');
const xsubs = '------^-------!';
const y = cold( '---d--e---f---|');
const ysubs = '--------------^-------------!';
const e1 = hot( '------x-------y------|', { x: x, y: y });
const expected = '--------a---b----d--e---f---|';
expectObservable(e1.switch()).toBe(expected);
expectSubscriptions(x.subscriptions).toBe(xsubs);
expectSubscriptions(y.subscriptions).toBe(ysubs);
- 将和的开头
xsubs
与ysubs
图表对齐expected
。 - 请注意,如何
x
同时取消e1
释放可观测的冷信号y
。
在大多数测试中,无需测试订阅点和取消订阅点,因为从 expected
图中可以明显看出或暗示该点。在那些情况下,请勿编写订阅声明。在具有多个内部订阅者或具有多个订阅者的冷可观察性的测试用例中,这些订阅断言可能很有用。
从测试生成 PNG 大理石图
通常,Jasmine 中的每个测试用例都写为it('should do something', function () { /* ... */ })
。要标记用于生成 PNG 图的测试用例,必须使用如下 asDiagram(label)
函数:
it.asDiagram(operatorLabel)('should do something', function () {
});
例如,使用 zip
,我们将编写
it.asDiagram('zip')('should zip by concatenating', function () {
const e1 = hot('---a---b---|');
const e2 = hot('-----c---d---|');
const expected = '-----x---y---|';
const values = { x: 'ac', y: 'bd' };
const result = e1.zip(e2, function(x, y) { return String(x) + String(y); });
expectObservable(result).toBe(expected, values);
});
然后,在运行时 npm run tests2png
,将分析该测试用例,并在该文件夹中创建一个 PNG 文件 zip.png
(由决定的文件名 ${operatorLabel}.png
)img/
。