我有一个包含这样的异步调用的方法:
public void MyMethod() {
...
(new Action<string>(worker.DoWork)).BeginInvoke(myString, null, null);
...
}
我正在使用Unity并且创建模拟对象不是问题,但是如何在不担心竞争条件的情况下测试DoWork的调用?
A previous question提供了一个解决方案,但在我看来,等待处理是一个黑客攻击(竞争条件仍然存在,虽然它几乎不可能提升)。
我想为上面提到的MyMethod创建一个测试,所以我做了类似的事情:
[TestMethod]
public void TestMyMethod() {
...setup...
MockWorker worker = new MockWorker();
MyObject myObj = new MyObject(worker);
...assert preconditions...
myObj.MyMethod();
...assert postconditions...
}
天真的方法是创建一个MockWorker(),它只是在调用DoWork时设置一个标志,并在后置条件中测试该标志。这当然会导致竞争条件,在MockWorker中设置标志之前检查后置条件。
更正确的方法(我可能最终会使用)是使用等待句柄:
class MockWorker : Worker {
public AutoResetEvent AutoResetEvent = new AutoResetEvent();
public override void DoWork(string arg) {
AutoResetEvent.Set();
}
}
...并使用以下断言:
Assert.IsTrue(worker.AutoResetEvent.WaitOne(1000, false));
这是使用类似信号量的方法,这很好......但在理论中可能会发生以下情况:
我是否误解了AutoResetEvent的工作原理?我只是太偏执了,还是有解决这个问题的聪明方法?
答案 0 :(得分:3)
等待处理将是我的方式。 AFAIK(我当然不知道)异步方法正在使用等待句柄来触发该方法。
我不确定为什么你会认为比赛条件会发生,除非你在WaitOne电话上给出了异常短的时间。我会在等待时间上放4-5秒,这样你就可以确定它是否被打破了,这不仅仅是一场比赛。
另外,不要忘记等待句柄是如何工作的,只要创建了等待句柄,就可以执行以下执行顺序
即使正常执行
要么正常工作,可以在Thread2开始等待之前设置等待句柄,然后为你处理所有事情。
答案 1 :(得分:3)
测试直接委托时,只需使用EndInvoke调用以确保调用委托并返回适当的值。例如。
var del = new Action<string>(worker.DoWork);
var async = del.BeginInvoke(myString,null,null);
...
var result = del.EndInvoke(async);
修改强>
OP评论说他们正在尝试对MyMethod和worker.DoWork方法进行单元测试。在这种情况下,您将不得不依赖于调用DoWork方法的可见副作用。根据你的例子,我不能在这里提供太多,因为DoWork的内部工作都没有暴露出来。
<强> EDIT2 强>
[OP]由于某种原因,主线程或DoWork线程都没有给出1000ms的执行时间。
这不会发生。当您在AutoResetEvent上调用WaitOne时,线程将进入休眠状态。在事件设置或超时期限到期之前,它将不会收到任何处理器时间。一些其他线程获得重要的时间片并导致错误的失败,这绝对是可行的。但我认为这是不太可能的。我有几个测试以相同的方式运行,我没有得到很多这样的错误失败。我通常选择超时约2分钟。
答案 2 :(得分:2)
这就是我在这些情况下所做的:创建一个接受委托并执行它的服务(通过一个简单的接口公开它,在异步调用东西的地方注入)。
在实际服务中,它将使用BeginInvoke执行,从而异步执行。然后创建一个用于测试的服务版本,以同步调用委托。
以下是一个例子:
public interface IActionRunner
{
void Run(Action action, AsyncCallback callback, object obj);
void Run<T>(Action<T> action, T arg, AsyncCallback callback, object obj);
void Run<T1, T2>(Action<T1, T2> action, T1 arg1, T2 arg2, AsyncCallback callback, object obj);
void Run<T1, T2, T3>(Action<T1, T2, T3> action, T1 arg1, T2 arg2, T3 arg3, AsyncCallback callback, object obj);
void Run<T1, T2, T3, T4>(Action<T1, T2, T3, T4> action, T1 arg1, T2 arg2, T3 arg3, T4 arg4, AsyncCallback callback, object obj);
}
此服务的Asycnhronous实现如下所示:
public void Run<T>(Action<T> action, T arg, AsyncCallback callback, object obj)
{
action.BeginInvoke(arg, callback, obj);
}
此服务的同步实现如下所示:
public void Run<T>(Action<T> action, T arg, AsyncCallback callback, object obj)
{
action(arg);
}
当您进行单元测试时,如果您使用的是RhinoMocks和AutoMocking容器之类的东西,则可以交换Synchronous ActionRunner:
_mocks = new MockRepository();
_container = new AutoMockingContainer(_mocks);
_container.AddService(typeof(IActionRunner), new SyncActionRunner());
_container.Initialize();
不需要测试ActionRunner;它只是方法调用的一个薄薄的贴面。