如何对包含异步调用的方法进行单元测试?

tox*_*erd 5 c# concurrency unit-testing

我有一个包含这样的异步调用的方法:

public void MyMethod() {
    ...
    (new Action<string>(worker.DoWork)).BeginInvoke(myString, null, null);
    ...
}
Run Code Online (Sandbox Code Playgroud)

我正在使用 Unity 并且创建模拟对象不是问题,但是如何测试 DoWork 被调用而不必担心竞争条件?

一个先前的问题提供了解决方案,但在我看来,等待句柄是一个黑客(比赛条件仍然存在,尽管它应该是几乎不可能提高)。


编辑:好的,我想我可以以非常笼统的方式问这个问题,但似乎我必须进一步详细说明这个问题:

我想为上面提到的 MyMethod 创建一个测试,所以我做这样的事情:

[TestMethod]
public void TestMyMethod() {
   ...setup...
   MockWorker worker = new MockWorker();
   MyObject myObj = new MyObject(worker);
   ...assert preconditions...
   myObj.MyMethod();
   ...assert postconditions...
}
Run Code Online (Sandbox Code Playgroud)

最简单的方法是创建一个 MockWorker(),它在调用 DoWork 时简单地设置一个标志,并在后置条件中测试该标志。这当然会导致竞争条件,即在 MockWorker 中设置标志之前检查后置条件。

更正确的方法(我可能最终会使用)是使用等待句柄:

class MockWorker : Worker {
    public AutoResetEvent AutoResetEvent = new AutoResetEvent();

    public override void DoWork(string arg) {
        AutoResetEvent.Set();
    }
}
Run Code Online (Sandbox Code Playgroud)

...并使用以下断言:

Assert.IsTrue(worker.AutoResetEvent.WaitOne(1000, false));
Run Code Online (Sandbox Code Playgroud)

这是使用类似信号量的方法,这很好……但理论上可能会发生以下情况:

  1. 我的 DoWork 委托调用了 BeginInvoke
  2. 出于某种原因,主线程或 DoWork 线程都没有 1000 毫秒的执行时间。
  3. 主线程被赋予执行时间,并且由于超时,断言失败,即使 DoWork 线程尚未执行。

我是否误解了 AutoResetEvent 的工作原理?是我太偏执了,还是有聪明的办法来解决这个问题?

All*_*ice 3

等待句柄将是我要做的方式。AFAIK(我不确定)异步方法正在使用等待句柄来触发该方法。

我不确定为什么您认为会发生竞争条件,除非您在 WaitOne 调用上给出的时间异常短。我会在 waitone 上放 4-5 秒,这样你就可以确定它是否坏了,而且这不仅仅是一场比赛。

另外,不要忘记等待句柄是如何工作的,只要创建了等待句柄,就可以有以下执行顺序

  • 线程 1 - 创建等待句柄
  • 线程 1 - 设置等待句柄
  • 线程 2 - 等待句柄上的 waitone
  • 线程 2 - 清除等待句柄并继续执行

即使正常执行是

  • 线程 1 - 创建等待句柄
  • 线程 2 - 等待句柄上的 waitone
  • 线程 1 - 设置等待句柄
  • 线程 2 - 清除等待句柄并继续执行

要么工作正常,等待句柄可以在 Thread2 开始等待之前设置,一切都会为您处理。