所以我有一些代码
Task.Factory.StartNew(() => this.listener.Start()).ContinueWith(
(task) =>
{
if (task.IsCompleted)
{
this.status = WorkerStatus.Started;
this.RaiseStatusChanged();
this.LogInformationMessage("Worker Started.");
}
});
当我测试时,我正在嘲笑所有因对象(namley this.listener.start())。问题在于,在继续调用继续执行之前执行的测试完成。当我调试时,由于我逐步浏览代码的额外延迟,因此被称为罚款。
那么,我如何 - 从不同的组件中的测试代码中 - 确保在我的测试列出其断言之前运行代码?
我只能使用thread.sleep ...但是这似乎是一种真正的技巧。
我想我正在寻找thread.join的任务版。
考虑以下内容:
public class SomeClass
{
public void Foo()
{
var a = new Random().Next();
}
}
public class MyUnitTest
{
public void MyTestMethod()
{
var target = new SomeClass();
target.Foo(); // What to assert, what is the result?..
}
}
分配给a
的值是多少?您不能说,除非结果在方法Foo()
之外返回(作为返回值,公共财产,事件等)。
"协调可预测结果的线程的动作"的过程称为同步。
在您的情况下,最简单的解决方案之一可能是返回Task
类的实例,并使用其Wait()
方法:
var task = Task.Factory.StartNew(() => Method1())
.ContinueWith(() => Method2());
无需等待第一个任务,因为continawith()创建了一个延续,该延续会在目标任务完成(msdn)时执行异步:
task.Wait();
我认为没有一种简单的实践方法。我本人刚刚遇到同一问题,thread.sleep(x)是迄今为止解决问题的最简单(如果不是优雅的)方式。
我考虑的唯一其他解决方案是隐藏任务。factory.startnew()在接口后面调用您可以从测试中嘲笑的接口,从而在测试方案中完全删除了任务的实际执行(但仍然有期望将调用接口方法。例如:
public interface ITaskWrapper
{
void TaskMethod();
}
和您的具体实现:
public class MyTask : ITaskWrapper
{
public void TaskMethod()
{
Task.Factory.StartNew(() => DoSomeWork());
}
}
然后只需在您的测试方法中模拟ITaskWrapper
,然后对TaskMethod
设置期望。
如果有任何方法可以通知处理何时结束(您可以添加该状态变化的事件的处理程序吗?),请使用ManualResetevent并在合理的超时上等待它。如果超时过期未能通过测试,则继续进行主张。
例如。
var waitHandle = new ManualResetEvent(false);
sut.StatusChanged += (s, e) => waitHandle.Set();
sut.DoStuff();
Assert.IsTrue(waitHandle.WaitOne(someTimeout), "timeout expired");
// do asserts here
无论是否在ContinueWith()
呼叫之前完成初始任务,连续任务仍将运行。我对以下内容进行了仔细检查:
// Task immediately exits
var task = Task.Factory.StartNew(() => { });
Thread.Sleep(100);
// Continuation on already-completed task
task.ContinueWith(t => { MessageBox.Show("!"); });
进一步调试。也许您的任务失败了。
在使用反应性扩展的测试中处理异步过程时,一种方法是使用测试仪。测试程序可以及时向前移动,耗尽所有固定任务等。因此,您正在测试的代码可以采用Ischeduler,您可以为其提供一个测试人员实例。然后,您的测试可以操纵时间,而无需实际睡觉,等待或同步。李·坎贝尔(Lee Campbell)的IschedulerProvider方法是对这种方法的改进。
如果您使用observable.start代替task.factory.startnew在代码中,则可以在单元测试中使用测试仪来推动所有计划的任务。
例如,您正在测试的代码看起来像这样:
//Task.Factory.StartNew(() => DoSomething())
// .ContinueWith(t => DoSomethingElse())
Observable.Start(() => DoSomething(), schedulerProvider.ThreadPool)
.ToTask()
.ContinueWith(t => DoSomethingElse())
在您的单位测试中:
// ... test code to execute the code under test
// run the tasks on the ThreadPool scheduler
testSchedulers.ThreadPool.Start();
// assertion code can now run