使用task.factory.startnew()的单元测试代码.继续()



所以我有一些代码

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