如何对从 Task.ContinueWith 派生出来的方法进行单元测试?
How can I unit test a method that spins off a Task.ContinueWith?
考虑以下代码:
public interface IBar
{
Task<IEnumerable<string>> GetStringsAsync();
}
public class Foo
{
public Foo(IBar bar, IList<string> initial)
{
MyCollection = new ObservableCollection<string>();
if (initial == null || !initial.Any())
AddContent(bar);
else
MyCollection.AddRange(initial);
}
public ObservableCollection<string> MyCollection { get; private set; }
public void AddContent(IBar bar)
{
var cancel = new CancellationTokenSource();
bar.GetStringsAsync().ContinueWith(
task => MyCollection.AddRange(task.Result),
cancel,
TaskContinuationOptions.NotOnCancel,
TaskScheduler.FromCurrentSynchronizationContext());
}
}
如何对 Foo.AddContent 方法进行单元测试?我想测试我的模拟 IBar 提供的字符串是否确实被添加到集合中,但断言总是在任务更新集合之前被调用。
我正在使用 .NET 4.5.2。我的第一选择是在 AddContent 中使用 async
和 await
,但是因为该方法是在构造函数中使用的,所以我认为最好避免这种情况。我需要一些可以启动异步数据加载但不会等待它完成的东西。
欢迎提出有关如何重写 AddContent 的建议,但我已经尝试了很多方法,这是唯一一个运行良好的方法,所以我真正想要的是一种测试它的方法。
看起来有问题的代码存在继承竞争条件。我会等待 GetStringAsync
完成并分配给 MyCollection
.
public void AddContent(IBar bar)
{
var cancel = new CancellationTokenSource();
var result = bar.GetStringsAsync().ContinueWith(
task => task.Result,
cancel,
TaskContinuationOptions.NotOnCancel,
TaskScheduler.FromCurrentSynchronizationContext());
MyCollection.AddRange(result.Result);
}
或者干脆
public void AddContent(IBar bar)
{
var result = bar.GetStringsAsync().Result;
MyCollection.AddRange(result);
}
更新 2
使用此处找到的 Stephen Cleary 的异步初始化模式。
http://blog.stephencleary.com/2013/01/async-oop-2-constructors.html
更新
由于问题已经改变,现在的要求是让构造函数接受变量IBar
。根据传递给构造函数的 IBar
变量的硬性要求,我建议如下:
public class Foo
{
public Foo(IBar bar)
{
MyCollection = new ObservableCollection<string>();
MyCollection.AddRange(bar.GetStringsAsync().Result));
}
public ObservableCollection<string> MyCollection { get; private set; }
public async Task AddContent(IBar bar)
{
MyCollection.AddRange(await bar.GetStringsAsync());
}
}
注意:public
方法仍然使用首选机制 async / await
,但构造函数只是调用 .Result
。这是构造函数中的 阻塞调用 并且是一种非常糟糕的做法。这很容易被认为是你永远不应该做的事情......
我强烈建议您的构造函数直接采用初始字符串(特别是考虑到它仅用于 returns!的字符串):
public class Foo
{
public Foo(IEnumerable<string> strings)
{
MyCollection = new ObservableCollection<string>();
MyCollection.AddRange(strings);
}
public ObservableCollection<string> MyCollection { get; private set; }
public async Task AddContent(IBar bar)
{
MyCollection.AddRange(await bar.GetStringsAsync());
}
}
用法
[TestMethod]
public async Task Test()
{
IBar bar = GetMockedBarImpl();
var sut = new Foo(await bar.GetStringsAsync());
Assert.IsTrue(sut.MyCollection.Any());
// TODO: Add asserts for known strings in collection...
}
考虑以下代码:
public interface IBar
{
Task<IEnumerable<string>> GetStringsAsync();
}
public class Foo
{
public Foo(IBar bar, IList<string> initial)
{
MyCollection = new ObservableCollection<string>();
if (initial == null || !initial.Any())
AddContent(bar);
else
MyCollection.AddRange(initial);
}
public ObservableCollection<string> MyCollection { get; private set; }
public void AddContent(IBar bar)
{
var cancel = new CancellationTokenSource();
bar.GetStringsAsync().ContinueWith(
task => MyCollection.AddRange(task.Result),
cancel,
TaskContinuationOptions.NotOnCancel,
TaskScheduler.FromCurrentSynchronizationContext());
}
}
如何对 Foo.AddContent 方法进行单元测试?我想测试我的模拟 IBar 提供的字符串是否确实被添加到集合中,但断言总是在任务更新集合之前被调用。
我正在使用 .NET 4.5.2。我的第一选择是在 AddContent 中使用 async
和 await
,但是因为该方法是在构造函数中使用的,所以我认为最好避免这种情况。我需要一些可以启动异步数据加载但不会等待它完成的东西。
欢迎提出有关如何重写 AddContent 的建议,但我已经尝试了很多方法,这是唯一一个运行良好的方法,所以我真正想要的是一种测试它的方法。
看起来有问题的代码存在继承竞争条件。我会等待 GetStringAsync
完成并分配给 MyCollection
.
public void AddContent(IBar bar)
{
var cancel = new CancellationTokenSource();
var result = bar.GetStringsAsync().ContinueWith(
task => task.Result,
cancel,
TaskContinuationOptions.NotOnCancel,
TaskScheduler.FromCurrentSynchronizationContext());
MyCollection.AddRange(result.Result);
}
或者干脆
public void AddContent(IBar bar)
{
var result = bar.GetStringsAsync().Result;
MyCollection.AddRange(result);
}
更新 2
使用此处找到的 Stephen Cleary 的异步初始化模式。
http://blog.stephencleary.com/2013/01/async-oop-2-constructors.html
更新
由于问题已经改变,现在的要求是让构造函数接受变量IBar
。根据传递给构造函数的 IBar
变量的硬性要求,我建议如下:
public class Foo
{
public Foo(IBar bar)
{
MyCollection = new ObservableCollection<string>();
MyCollection.AddRange(bar.GetStringsAsync().Result));
}
public ObservableCollection<string> MyCollection { get; private set; }
public async Task AddContent(IBar bar)
{
MyCollection.AddRange(await bar.GetStringsAsync());
}
}
注意:public
方法仍然使用首选机制 async / await
,但构造函数只是调用 .Result
。这是构造函数中的 阻塞调用 并且是一种非常糟糕的做法。这很容易被认为是你永远不应该做的事情......
我强烈建议您的构造函数直接采用初始字符串(特别是考虑到它仅用于 returns!的字符串):
public class Foo
{
public Foo(IEnumerable<string> strings)
{
MyCollection = new ObservableCollection<string>();
MyCollection.AddRange(strings);
}
public ObservableCollection<string> MyCollection { get; private set; }
public async Task AddContent(IBar bar)
{
MyCollection.AddRange(await bar.GetStringsAsync());
}
}
用法
[TestMethod]
public async Task Test()
{
IBar bar = GetMockedBarImpl();
var sut = new Foo(await bar.GetStringsAsync());
Assert.IsTrue(sut.MyCollection.Any());
// TODO: Add asserts for known strings in collection...
}