Модульный тест для асинхронного вызова делегата в c #

У меня есть функция, которая создала делегированный объект и запускает BeginInvoke для этого объекта, а другая функция передается для ожидания EndInvoke:

    private static void DeploymentComponentThreadedCallBack(IAsyncResult ar)
    {
        var result = (AsyncResult)ar;
        var pluginExecuteAction = (Action<int, Guid, int, EnvironmentServerComponentSet, string>)result.AsyncDelegate;
        pluginExecuteAction.EndInvoke(ar);
        //report back to WCF service that thread is finished
    }

public void DeployComponent(byte[] resource, Guid componentGuid, string deploymentType, Dictionary<string, object> args)
{
  var asyncCallback = new AsyncCallback(DeploymentComponentThreadedCallBack);
  IDeployComponent plugin = GetPluginDelegate();
  Action<byte[], Guid, string, Dictionary<string, object>> pluginExecuteAction = plugin.DeployComponent;
  IAsyncResult ar = pluginExecuteAction.BeginInvoke(resource, componentGuid, deploymentType, args, asyncCallback, null);
}

Я хотел бы провести модульное тестирование, но когда я это сделаю, DeploymentComponentThreadedCallBack никогда не сработает, и, очевидно, тоже не вызывает EndInvoke. Я предполагаю, что это происходит потому, что тест проходит до завершения асинхронного потока, поэтому поток перестает выполняться до EndInvoke, но есть ли способ остановить это, чтобы я мог видеть, что EndInvoke попадает?

Привет, Мэтт


person Bob Tway    schedule 31.08.2011    source источник


Ответы (3)


Я думаю, ваша основная проблема в том, что вы не раскрываете в методе DeployComponent ничего, что позволило бы вам отслеживать асинхронную операцию, которую вы там запускаете. Если вы вернули IAsyncResult оттуда, вы можете вызвать ar.AsyncWaitHandle.WaitOne(), чтобы дождаться завершения.

person RandomEngy    schedule 31.08.2011
comment
К сожалению, это образец кода - на самом деле DeployComponent делает гораздо больше, и у него уже есть возвращаемый тип bool. Спасибо за предложение. - person Bob Tway; 01.09.2011
comment
Что ж, тебе придется как-то решить эту проблему. Если вы хотите дождаться завершения операции, вам нужно открыть что-то для вызывающего абонента. При нормальных обстоятельствах вы продолжаете и не ждете завершения, но в своем тесте вы ждете его завершения. На самом деле вы можете просто изменить DeployComponent, чтобы он следил за APM и сообщал о завершении, когда все, что он запускает, завершено. Может потребоваться небольшой рефакторинг. - person RandomEngy; 02.09.2011

Насколько я помню, AsyncResult имеет флаг (IsCompleted), который сообщает вам, продолжается ли операция. Подождите (например, примитивно с циклом while), а затем сделайте свои утверждения

person flq    schedule 31.08.2011
comment
Такой флаг есть, но как мне добраться до него в вызывающем объекте? т.е. если метод DeployComponent находится на объекте Foo, и в моем тесте я вызываю Foo.DeployComponent (byteStream, new Guid (), cmd, null); где я могу получить AsyncResult, чтобы увидеть, завершено ли оно? - person Bob Tway; 31.08.2011
comment
Я не вижу, откуда вы идете. Вы передаете Asyncresult в первый метод, чтобы вы могли дождаться его, прежде чем переходить к нему. - person flq; 31.08.2011
comment
А, я понимаю, что ты имеешь в виду. Это не сработает, так как при нормальных обстоятельствах я не хочу этого ждать - только для этого теста. В противном случае не было бы особого смысла делать это асинхронно. - person Bob Tway; 31.08.2011
comment
Мэтт, чего не хватает, так это твоего теста - не видно, откуда ты. Если вы не предоставите себе доступ к AsyncHandle, вам, вероятно, не повезло - person flq; 31.08.2011

Вам просто нужно сделать точку внедрения, чтобы превратить асинхронные вызовы в блокирующие вызовы. Например:

 public class MethodInvoker
 {
     public virtual void Invoke(Action begin, Action<IAsyncResult> end)
     {
          begin.BeginInvoke(end, null);
     }
 }

С версией модульного тестирования, например:

 public class SynchronousInvoker : MethodInvoker
 {
     public override void Invoke(Action begin, Action<IAsyncResult> end)
     {
         begin();
         end();
     }
 }

Тогда вы должны написать такой код:

 _myMethodInvoker.Invoke(pluginExecuteAction, asyncCallback);

Что в контексте ваших обычных функций является асинхронным. В своих модульных тестах вы просто вставляете SynchronousInvoker на его место, и он становится блокирующим вызовом.

person Tejs    schedule 31.08.2011
comment
Это похоже на хороший подход, но я не могу настроить его, чтобы он работал. BeginInvoke требует параметра типа AsyncResult, а не Action ‹IAsyncResult›, и без действия вы не можете вызвать для него end (). - person Bob Tway; 01.09.2011