[英]Techniques for testing code that uses multiple schedulers
當SUT依賴於多個調度程序時,保持測試代碼簡潔和集中的最佳方法是什么? 也就是說,避免虛假調用來推進多個不同的調度程序。
到目前為止,我的技術是定義一個提供調度程序的應用程序級服務:
public interface ISchedulerService
{
IScheduler DefaultScheduler { get; }
IScheduler SynchronizationContextScheduler { get; }
IScheduler TaskPoolScheduler { get; }
// other schedulers
}
然后,應用程序組件注入了ISchedulerService
實例,對於需要調度程序的任何響應管道,它都是從服務中獲取的。 然后,測試代碼可以使用TestSchedulerService
的實例:
public sealed class TestSchedulerService : ISchedulerService
{
private readonly TestScheduler defaultScheduler;
private readonly TestScheduler synchronizationContextScheduler;
private readonly TestScheduler taskPoolScheduler;
// other schedulers
public TestSchedulerService()
{
this.defaultScheduler = new TestScheduler();
this.synchronizationContextScheduler = new TestScheduler();
this.taskPoolScheduler = new TestScheduler();
}
public IScheduler DefaultScheduler
{
get { return this.defaultScheduler; }
}
public IScheduler SynchronizationContextScheduler
{
get { return this.synchronizationContextScheduler; }
}
public IScheduler TaskPoolScheduler
{
get { return this.taskPoolScheduler; }
}
public void Start()
{
foreach (var testScheduler in this.GetTestSchedulers())
{
testScheduler.Start();
}
}
public void AdvanceBy(long time)
{
foreach (var testScheduler in this.GetTestSchedulers())
{
testScheduler.AdvanceBy(time);
}
}
public void AdvanceTo(long time)
{
foreach (var testScheduler in this.GetTestSchedulers())
{
testScheduler.AdvanceTo(time);
}
}
private IEnumerable<TestScheduler> GetTestSchedulers()
{
yield return this.defaultScheduler;
yield return this.synchronizationContextScheduler;
yield return this.taskPoolScheduler;
// other schedulers
}
}
然后,測試代碼可以控制時間:
var scheduler = new TestSchedulerService();
var sut = new SomeClass(scheduler);
scheduler.AdvanceBy(...);
但是,我發現當SUT使用多個調度程序時,這可能會導致問題。 考慮這個簡單的例子:
[Fact]
public void repro()
{
var scheduler1 = new TestScheduler();
var scheduler2 = new TestScheduler();
var pipeline = Observable
.Return("first")
.Concat(
Observable
.Return("second")
.Delay(TimeSpan.FromSeconds(1), scheduler2))
.ObserveOn(scheduler1);
string currentValue = null;
pipeline.Subscribe(x => currentValue = x);
scheduler1.AdvanceBy(TimeSpan.FromMilliseconds(900).Ticks);
scheduler2.AdvanceBy(TimeSpan.FromMilliseconds(900).Ticks);
Assert.Equal("first", currentValue);
scheduler1.AdvanceBy(TimeSpan.FromMilliseconds(100).Ticks);
scheduler2.AdvanceBy(TimeSpan.FromMilliseconds(100).Ticks);
Assert.Equal("second", currentValue);
}
這里SUT使用兩個調度器 - 一個控制延遲,另一個控制觀察訂閱的線程。 此測試實際上失敗,因為調度程序以錯誤的順序前進。 第二個延遲(100ms)有多大並不重要 - 在scheduler1
之前scheduler2
scheduler1
已進入意味着訂閱代碼(由scheduler1
控制)將不會被執行。 我們需要另一個調用來推進scheduler1
(或啟動它)。
顯然在上面的測試代碼中,我可以將調用交換到AdvanceBy
並且它可以工作。 然而,實際上我是通過它注入我的服務和控制時間。 它需要為調度程序選擇一個特定的順序,並且沒有真正的方法來知道“正確”順序是什么 - 這取決於SUT。
人們使用什么技術來解決這個問題? 我能想到這些:
TestScheduler
在TestSchedulerService
並從所有調度屬性返回它
TestSchedulerService
接受一個構造函數參數,告訴它是創建多個TestScheduler
實例,還是只創建一個。 默認只使用一個,因為根據我的經驗,需要多個的測試更少見
TestSchedulerService
復雜化了一些 我傾向於那里的最終選項(並且已經突出了代碼)。 但我想知道是否有更清晰/更清潔的方法來處理這個問題?
正如你所暗示的那樣,這有點“依賴於”情況。 我認為你的分析很好。 用於DI調度程序的ISchedulerService
方法是合理的,我已經在多個項目中成功使用它。
根據我的個人經驗,在測試中需要多個不同的TestScheduler是非常罕見的 - 我已經寫了很多涉及Rx的單元測試,並且需要在大約5個左右的時間內執行此操作。
因此,我默認使用單個TestScheduler,並且沒有用於管理多個TestScheduler方案的特定基礎結構。
以它們為特色的測試通常會突出顯示非常具體的邊緣情況,並且只需仔細編寫並進行大量評論。
我懷疑沒有編寫這些測試的用戶會欣賞操縱調度程序而不是隱藏在框架后面的細節,因為在這些情況下你需要盡可能清楚地看到正在發生的事情。
出於這個原因,我認為我會堅持在我需要的測試代碼中直接操作多個調度程序。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.