Итак, когда я разрабатываю новую функцию для своей системы, я тоже пытаюсь сделать TDD - код слишком большой, чтобы делать это для старых функций прямо сейчас, к сожалению.
Однако я обнаружил, что иногда я сталкиваюсь с кирпичной стеной во время испытаний - особенно при использовании Delay
и Throttle
.
Я много читал, и я думаю, что знаю намного больше, чем неделю назад, но я хотел применить все это на практике. Я написал несколько экспериментов:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Reactive.Testing;
using NUnit.Framework;
using NUnit.Framework.Internal.Commands;
using ReactiveUI;
using ReactiveUI.Testing;
namespace UtilsTests
{
[TestFixture]
public class SchedulersTests
{
private int SecondsN = 1;
[Test]
public async Task NoScheduler()
{
var t = Observable.Return(Unit.Default).Delay(TimeSpan.FromSeconds(SecondsN), RxApp.MainThreadScheduler)
.ObserveOn(RxApp.MainThreadScheduler)
.ToTask();
await t;
}
[Test]
public Task ImmediateSchedulerExperiment()
{
return Scheduler.Immediate.With(async s =>
{
var t = Observable.Return(Unit.Default).Delay(TimeSpan.FromSeconds(SecondsN), RxApp.MainThreadScheduler).ToTask();
await t;
});
}
[Test]
public Task ImmediateSchedulerExperiment2()
{
return Scheduler.Immediate.With(async s =>
{
var t = Observable.Return(Unit.Default).Delay(TimeSpan.FromSeconds(SecondsN), s).FirstAsync().ToTask();
await t;
});
}
[Test]
public void ImmediateSchedulerExperiment3()
{
Scheduler.Immediate.With(s =>
{
var t = false;
Observable.Return(Unit.Default).Delay(TimeSpan.FromSeconds(SecondsN), s)
.Subscribe(_ =>
{
t = true;
});
Assert.IsTrue(t);
});
}
[Test]
public void TestSchedulerExperiment_SchedulersNotSpecified()
{
new TestScheduler().With(s =>
{
var t = false;
Observable.Return(Unit.Default).Delay(TimeSpan.FromSeconds(SecondsN), s)
.Subscribe(_ =>
{
t = true;
});
s.AdvanceByMs(SecondsN * 1000);
Assert.IsTrue(t);
});
}
[Test]
public void TestSchedulerExperiment_DeylaOn_RxMainThread()
{
new TestScheduler().With(s =>
{
var t = false;
Observable.Return(Unit.Default).Delay(TimeSpan.FromSeconds(SecondsN), RxApp.MainThreadScheduler)
.Subscribe(_ =>
{
t = true;
});
s.AdvanceByMs(SecondsN * 1000);
Assert.IsTrue(t);
});
}
[Test]
public void TestSchedulerExperiment_DeylaOn_RxTaskPool()
{
new TestScheduler().With(s =>
{
var t = false;
Observable.Return(Unit.Default).Delay(TimeSpan.FromSeconds(SecondsN), RxApp.TaskpoolScheduler)
.Subscribe(_ =>
{
t = true;
});
s.AdvanceByMs(SecondsN * 1000);
Assert.IsTrue(t);
});
}
[Test]
public void TestSchedulerExperiment_RunOnTaskPool_ObserveOnMainThread()
{
new TestScheduler().With(s =>
{
var t = false;
Observable.Return(Unit.Default)
.Delay(TimeSpan.FromSeconds(SecondsN), RxApp.TaskpoolScheduler)
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(_ =>
{
t = true;
});
s.AdvanceByMs(SecondsN * 1000);
Assert.IsTrue(t);
});
}
[Test]
public void TestSchedulerExperiment_RunOnTaskPool_ObserveOnTaskpool()
{
new TestScheduler().With(s =>
{
var t = false;
Observable.Return(Unit.Default)
.Delay(TimeSpan.FromSeconds(SecondsN), RxApp.TaskpoolScheduler)
.ObserveOn(RxApp.TaskpoolScheduler)
.Subscribe(_ =>
{
t = true;
});
s.AdvanceByMs(SecondsN * 1000);
s.AdvanceByMs(1);
Assert.IsTrue(t);
});
}
[Test]
public void TestSchedulerExperiment_RunOnTaskPool_ObserveOnMainThread_MainThreadIsAnotherInstance()
{
new TestScheduler().With(s =>
{
var mainThreadScheduler = new TestScheduler();
RxApp.MainThreadScheduler = mainThreadScheduler;
var t = false;
Observable.Return(Unit.Default)
.Delay(TimeSpan.FromSeconds(SecondsN), RxApp.TaskpoolScheduler)
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(_ =>
{
t = true;
});
s.AdvanceByMs(SecondsN * 1000);
mainThreadScheduler.AdvanceBy(1);
Assert.IsTrue(t);
});
}
[Test]
public void TestSchedulerExperiment_RunOnTest_ObserveOnTest()
{
new TestScheduler().With(s =>
{
var t = false;
var obs = Observable.Return(Unit.Default)
.Delay(TimeSpan.FromSeconds(SecondsN), s)
.ObserveOn(s);
obs
.Subscribe(_ =>
{
t = true;
});
// s.AdvanceByMs(SecondsN * 1000);
// s.AdvanceBy(1);
s.AdvanceUntil(obs);
Assert.IsTrue(t);
});
}
}
}
Сначала я думал, что Scheduler.Immediate
сделает свое дело, выполняя вещи с задержкой прямо на месте, и мальчик, это неправильно. Я нашел эту статью, которая довольно хорошо объясняла вещи. Я нашел также этот пост, объясняющий, какой оператор использует какой планировщик.
Теперь я знаю, что при игре со временем я должен использовать TestScheduler. В противном случае не меняйте расписание.
Теперь я знаю, что вы НЕ делаете ничего асинхронного в contructor, вместо этого вы создаете команду, скажем, скажем Init
, которая делает это при активации, и вы можете ожидать ее в тесте (например, создание отложенной коллекции на основе конструктора). аргумент, разрешающий плавную анимацию пользовательского интерфейса, когда представление завершено)
НО, когда я запускаю эти тесты сверху, я получаю это:
![Test results](https://i.stack.imgur.com/g05qv.png)
Есть несколько вещей, которые я не понимаю.
1) Почему при Scheduler.Immediate
тесты занимают вдвое больше времени? Я думаю, я понимаю, почему Take(1)
не имеет значения, но все же ...
2) Как использовать TestSchduler, как определить, на какой шаг шаг вперед?
Я заметил, что в тесте TestSchedulerExperiment_RunOnTest_ObserveOnTest
я должен сделать дополнительные AdvanceBy(1)
, потому что это также наблюдатель. Поэтому, когда цепь длиннее, у нее больше наблюдателей, их действительно сложно сосчитать.
Это обычная практика scheduler.AdvanceBy(10000000000000);
?
Я пытался создать расширение AdvanceUntil
, но я знаю, что оно отстой по многим причинам (например, наблюдаемые в холодном режиме).
public static void AdvanceUntil<TIgnore>(this TestScheduler s, IObservable<TIgnore> obs, double? advanceByMs = null)
{
var done = false;
obs.Subscribe(_ => done = true, (ex) => done = true, () => done = true);
while(!done)
s.AdvanceByMs(advanceByMs ?? 100);
}
Или, может быть, есть метод "сброса", которого я не знаю?
Кроме того, я научился ждать вещи внутри TestScheduler.With
:
[Test]
public Task TestSchedulerExperiment_await()
{
return new TestScheduler().With(async s =>
{
var v = false;
var t = Observable.Return(true).Delay(TimeSpan.FromSeconds(SecondsN), s)
.Take(1) // without hits the test never ends
.ToTask();
s.AdvanceByMs(SecondsN * 1000);
v = await t;
Assert.IsTrue(v);
});
но мне все еще нужно знать время.
А почему там должно быть Take(1)
?