我尝试测试函数Stream transform(Stream input)
。如何测试返回的流是否在特定时间发出元素?
在RxJS(JavaScript)中,我可以使用TestScheduler在特定时间在输入流上发出元素,并测试它们是否在特定时间在输出流上发出。在此example中,转换函数将传递给scheduler.startWithCreate
:
var scheduler = new Rx.TestScheduler();
// Create hot observable which will start firing
var xs = scheduler.createHotObservable(
onNext(150, 1),
onNext(210, 2),
onNext(220, 3),
onCompleted(230)
);
// Note we'll start at 200 for subscribe, hence missing the 150 mark
var res = scheduler.startWithCreate(function () {
return xs.map(function (x) { return x * x });
});
// Implement collection assertion
collectionAssert.assertEqual(res.messages, [
onNext(210, 4),
onNext(220, 9),
onCompleted(230)
]);
// Check for subscribe/unsubscribe
collectionAssert.assertEqual(xs.subscriptions, [
subscribe(200, 230)
]);
答案 0 :(得分:1)
更新:将我的代码发布为名为stream_test_scheduler的软件包。
此代码与TestScheduler中的RxJS类似,但它使用实时(毫秒)而非虚拟时间,因为您无法在Dart中伪造时间(请参阅Irn's comment) 。您可以将最大偏差传递给匹配器。我在这个例子中使用了20毫秒。但偏差各不相同。您可能必须为另一个测试或另一个(更快/更慢)系统使用不同的最大偏差值。
编辑:我将示例更改为延迟转换函数,该函数是delay包的stream_ext函数的较短(可配置/参数较少)版本。测试检查元素是否延迟了一秒钟。
import 'dart:async';
import 'package:test/test.dart';
// To test:
/// Modified version of <https://github.com/theburningmonk/stream_ext/wiki/delay>
Stream delay(Stream input, Duration duration) {
var controller = new StreamController.broadcast(sync : true);
delayCall(Function f, [Iterable args]) => args == null
? new Timer(duration, f)
: new Timer(duration, () => Function.apply(f, args));
input.listen(
(x) => delayCall(_tryAdd, [controller, x]),
onError : (ex) => delayCall(_tryAddError, [ex]),
onDone : () => delayCall(_tryClose, [controller])
);
return controller.stream;
}
_tryAdd(StreamController controller, event) {
if (!controller.isClosed) controller.add(event);
}
_tryAddError(StreamController controller, err) {
if (!controller.isClosed) controller.addError(err);
}
_tryClose(StreamController controller) {
if (!controller.isClosed) controller.close();
}
main() async {
test('delay preserves relative time intervals between the values', () async {
var scheduler = new TestScheduler();
var source = scheduler.createStream([
onNext(150, 1),
onNext(210, 2),
onNext(220, 3),
onCompleted(230)
]);
var result = await scheduler.startWithCreate(() => delay(source, ms(1000)));
expect(result, equalsRecords([
onNext(1150, 1),
onNext(1210, 2),
onNext(1220, 3),
onCompleted(1230)
], maxDeviation: 20));
});
}
equalsRecords(List<Record> records, {int maxDeviation: 0}) {
return pairwiseCompare(records, (Record r1, Record r2) {
var deviation = (r1.ticks.inMilliseconds - r2.ticks.inMilliseconds).abs();
if (deviation > maxDeviation) {
return false;
}
if (r1 is OnNextRecord && r2 is OnNextRecord) {
return r1.value == r2.value;
}
if (r1 is OnErrorRecord && r2 is OnErrorRecord) {
return r1.exception == r2.exception;
}
return (r1 is OnCompletedRecord && r2 is OnCompletedRecord);
}, 'equal with deviation of ${maxDeviation}ms to');
}
class TestScheduler {
final SchedulerTasks _tasks;
TestScheduler() : _tasks = new SchedulerTasks();
Stream createStream(List<Record> records) {
final controller = new StreamController(sync: true);
_tasks.add(controller, records);
return controller.stream;
}
Future<List<Record>> startWithCreate(Stream createStream()) {
final completer = new Completer<List<Record>>();
final records = <Record>[];
final start = new DateTime.now();
int timeStamp() {
final current = new DateTime.now();
return current.difference(start).inMilliseconds;
}
createStream().listen(
(event) => records.add(onNext(timeStamp(), event)),
onError: (exception) => records.add(onError(timeStamp(), exception)),
onDone: () {
records.add(onCompleted(timeStamp()));
completer.complete(records);
}
);
_tasks.run();
return completer.future;
}
}
class SchedulerTasks {
Map<Record, StreamController> _controllers = {};
List<Record> _records = [];
void add(StreamController controller, List<Record> records) {
for (var record in records) {
_controllers[record] = controller;
}
_records.addAll(records);
}
void run() {
_records.sort();
for (var record in _records) {
final controller = _controllers[record];
new Future.delayed(record.ticks, () {
if (record is OnNextRecord) {
controller.add(record.value);
} else if (record is OnErrorRecord) {
controller.addError(record.exception);
} else if (record is OnCompletedRecord) {
controller.close();
}
});
}
}
}
onNext(int ticks, int value) => new OnNextRecord(ms(ticks), value);
onCompleted(int ticks) => new OnCompletedRecord(ms(ticks));
onError(int ticks, exception) => new OnErrorRecord(ms(ticks), exception);
Duration ms(int milliseconds) => new Duration(milliseconds: milliseconds);
abstract class Record implements Comparable {
final Duration ticks;
Record(this.ticks);
@override
int compareTo(other) => ticks.compareTo(other.ticks);
}
class OnNextRecord extends Record {
final value;
OnNextRecord(Duration ticks, this.value) : super (ticks);
@override
String toString() => 'onNext($value)@${ticks.inMilliseconds}';
}
class OnErrorRecord extends Record {
final exception;
OnErrorRecord(Duration ticks, this.exception) : super (ticks);
@override
String toString() => 'onError($exception)@${ticks.inMilliseconds}';
}
class OnCompletedRecord extends Record {
OnCompletedRecord(Duration ticks) : super (ticks);
@override
String toString() => 'onCompleted()@${ticks.inMilliseconds}';
}