13

所以我有一些代码

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())。问题是测试在调用 ContinueWith 之前完成执行。当我调试时,由于我单步执行代码的额外延迟,它被调用得很好。

那么我如何 - 从不同程序集中的测试代码 - 确保代码在我的测试达到其断言之前运行?

我可以只使用 Thread.Sleep ...但这似乎是一种非常老套的方法。

我想我正在寻找 Thread.Join 的任务版本。

4

5 回答 5

2

考虑以下:

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());

无需等待第一个任务,因为ContinueWith()创建了一个在目标任务完成时异步执行的延续( MSDN ):

task.Wait();
于 2012-11-09T13:34:41.640 回答
2

我认为没有一种简单而实用的方法可以做到这一点。刚才我自己也遇到了同样的问题,而 Thread.Sleep(X) 是迄今为止解决问题的最简单(如果不是优雅)的方法。

我考虑的唯一其他解决方案是将 Task.Factory.StartNew() 调用隐藏在可以从测试中模拟的接口后面,从而完全在测试场景中删除任务的实际执行(但仍然期望接口方法将被调用。例如:

public interface ITaskWrapper
{
    void TaskMethod();
}

而你的具体实现:

public class MyTask : ITaskWrapper
{
    public void TaskMethod()
    {
        Task.Factory.StartNew(() => DoSomeWork());
    }
}

然后只需模拟您的测试方法并设置被调用ITaskWrapper的期望。TaskMethod

于 2013-10-09T15:02:36.750 回答
1

如果有任何方法可以在处理结束时通知您(您可以为该 StatusChanged 事件添加处理程序吗?),请使用 ManualResetEvent 并在合理的超时时间内等待它。如果超时过期,则测试失败,否则继续执行您的断言。

例如

var waitHandle = new ManualResetEvent(false);
sut.StatusChanged += (s, e) => waitHandle.Set();

sut.DoStuff();

Assert.IsTrue(waitHandle.WaitOne(someTimeout), "timeout expired");
// do asserts here
于 2012-11-09T13:31:32.450 回答
0

ContinueWith()无论初始任务是否在调用之前完成,延续任务仍将运行。我仔细检查了以下内容:

// Task immediately exits
var task = Task.Factory.StartNew(() => { });

Thread.Sleep(100);

// Continuation on already-completed task
task.ContinueWith(t => { MessageBox.Show("!"); });

进一步调试。也许你的任务失败了。

于 2012-11-09T13:33:37.803 回答
0

在使用响应式扩展的被测代码期间处理异步进程时,一种方法是使用 TestScheduler。TestScheduler 可以及时向前移动,耗尽所有已调度的任务等。因此,您的测试代码可以采用 IScheduler,您可以为其提供 TestScheduler 实例。然后,您的测试可以操纵时间,而无需实际休眠、等待或同步。这种方法的改进是Lee Campbell 的 ISchedulerProvider方法。

如果您在代码中使用 Observable.Start 而不是 Task.Factory.StartNew,那么您可以在单元测试中使用 TestScheduler 来推送所有计划任务。

例如,您的测试代码可能如下所示:

//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
于 2014-10-01T01:27:26.993 回答