ger*_*rmi 1 c# unit-testing system.reactive akavache
所以我试图在使用Akavache的应用程序中测试缓存行为.我的测试看起来像这样:
using Akavache;
using Microsoft.Reactive.Testing;
using Moq;
using NUnit.Framework;
using ReactiveUI.Testing;
using System;
using System.Threading.Tasks;
[TestFixture]
public class CacheFixture
{
[Test]
public async Task CachingTest()
{
var scheduler = new TestScheduler();
// replacing the TestScheduler with the scheduler below works
// var scheduler = CurrentThreadScheduler.Instance;
var cache = new InMemoryBlobCache(scheduler);
var someApi = new Mock<ISomeApi>();
someApi.Setup(s => s.GetSomeStrings())
.Returns(Task.FromResult("helloworld")).Verifiable();
var apiWrapper = new SomeApiWrapper(someApi.Object, cache,
TimeSpan.FromSeconds(10));
var string1 = await apiWrapper.GetSomeStrings();
someApi.Verify(s => s.GetSomeStrings(), Times.Once());
StringAssert.AreEqualIgnoringCase("helloworld", string1);
scheduler.AdvanceToMs(5000);
// without the TestScheduler, I'd have to 'wait' here
// await Task.Delay(5000);
var string2 = await apiWrapper.GetSomeStrings();
someApi.Verify(s => s.GetSomeStrings(), Times.Once());
StringAssert.AreEqualIgnoringCase("helloworld", string2);
}
}
Run Code Online (Sandbox Code Playgroud)
SomeApiWrapper使用内部api(模拟new Mock<ISomeApi>()) - 为简单起见 - 只返回一个字符串.现在的问题是永远不会返回第二个字符串.SomeApiWrapper处理缓存的类看起来像这样:
using Akavache;
using System;
using System.Reactive.Linq;
using System.Threading.Tasks;
public class SomeApiWrapper
{
private IBlobCache Cache;
private ISomeApi Api;
private TimeSpan Timeout;
public SomeApiWrapper(ISomeApi api, IBlobCache cache, TimeSpan cacheTimeout)
{
Cache = cache;
Api = api;
Timeout = cacheTimeout;
}
public async Task<string> GetSomeStrings()
{
var key = "somestrings";
var cachedStrings = Cache.GetOrFetchObject(key, DoGetStrings,
Cache.Scheduler.Now.Add(Timeout));
// this is the last step, after this it just keeps running
// but never returns - but only for the 2nd call
return await cachedStrings.FirstOrDefaultAsync();
}
private async Task<string> DoGetStrings()
{
return await Api.GetSomeStrings();
}
}
Run Code Online (Sandbox Code Playgroud)
调试只会让我走到这一行return await cachedStrings.FirstOrDefaultAsync();- 它永远不会在那之后完成.
当我用TestScheduler标准(CurrentThreadScheduler.Instance)和scheduler.AdvanceToMs(5000)with替换时await Task.Delay(5000),一切都按预期工作,但我不希望单元测试运行多秒.
TestScheduler超过缓存超时的类似测试也会成功.就是这种情况,缓存条目不应该在两个方法调用之间到期.
我正在使用的方式有什么问题TestScheduler吗?
当Task在IObservable范例和范例之间反弹时,这是一个相当普遍的问题.在试验中向前迈进之前,试图等待它会进一步恶化.
关键问题是你在这里阻止*
return await cachedStrings.FirstOrDefaultAsync();
Run Code Online (Sandbox Code Playgroud)
我说阻塞的意思是代码不能继续处理,直到这个语句产生.
在第一次运行时,缓存无法找到密钥,因此它会执行您的密钥DoGetStrings.问题在第二次运行时浮出水面,其中填充了缓存.这次(我猜)调度了缓存数据的提取.您需要调用请求,观察序列,然后泵送调度程序.
更正的代码在这里(但需要一些API更改)
[TestFixture]
public class CacheFixture
{
[Test]
public async Task CachingTest()
{
var testScheduler = new TestScheduler();
var cache = new InMemoryBlobCache(testScheduler);
var cacheTimeout = TimeSpan.FromSeconds(10);
var someApi = new Mock<ISomeApi>();
someApi.Setup(s => s.GetSomeStrings())
.Returns(Task.FromResult("helloworld")).Verifiable();
var apiWrapper = new SomeApiWrapper(someApi.Object, cache, cacheTimeout);
var string1 = await apiWrapper.GetSomeStrings();
someApi.Verify(s => s.GetSomeStrings(), Times.Once());
StringAssert.AreEqualIgnoringCase("helloworld", string1);
testScheduler.AdvanceToMs(5000);
var observer = testScheduler.CreateObserver<string>();
apiWrapper.GetSomeStrings().Subscribe(observer);
testScheduler.AdvanceByMs(cacheTimeout.TotalMilliseconds);
someApi.Verify(s => s.GetSomeStrings(), Times.Once());
StringAssert.AreEqualIgnoringCase("helloworld", observer.Messages[0].Value.Value);
}
}
public interface ISomeApi
{
Task<string> GetSomeStrings();
}
public class SomeApiWrapper
{
private IBlobCache Cache;
private ISomeApi Api;
private TimeSpan Timeout;
public SomeApiWrapper(ISomeApi api, IBlobCache cache, TimeSpan cacheTimeout)
{
Cache = cache;
Api = api;
Timeout = cacheTimeout;
}
public IObservable<string> GetSomeStrings()
{
var key = "somestrings";
var cachedStrings = Cache.GetOrFetchObject(key, DoGetStrings,
Cache.Scheduler.Now.Add(Timeout));
//Return an observerable here instead of "blocking" with a task. -LC
return cachedStrings.Take(1);
}
private async Task<string> DoGetStrings()
{
return await Api.GetSomeStrings();
}
}
Run Code Online (Sandbox Code Playgroud)
此代码为绿色,运行低于秒.