2016-11-26 5 views
0

Я написал класс C#, который находится между пользовательским интерфейсом приложения WPF и системой обмена сообщениями Async. Теперь я пишу Unit Tests для этого класса и сталкиваюсь с проблемами с диспетчером. Следующий метод относится к тестируемому классу, и он создает обработчик подписки. Таким образом, я вызываю этот метод Set_Listing_Records_ResponseHandler из Тестирования единиц измерения.Модульное тестирование класса, используемого в многопоточном приложении WPF

public async Task<bool> Set_Listing_Records_ResponseHandler(
    string responseChannelSuffix, 
    Action<List<AIDataSetListItem>> successHandler, 
    Action<Exception> errorHandler) 
{ 
    // Subscribe to Query Response Channel and Wire up Handler for Query Response 
    await this.ConnectAsync(); 
    return await this.SubscribeTo_QueryResponseChannelAsync(responseChannelSuffix, new FayeMessageHandler(delegate (FayeClient client, FayeMessage message) { 
     Application.Current.Dispatcher.BeginInvoke(new Action(() => 
     { 
      try 
      { 
       ... 
      } 
      catch (Exception e) 
      { 
       ... 
      } 
     })); 
    })); 
} 

поток выполнения возвращается к .... линии Application.Current.Dispatcher но потом выкидывает ошибку:

Object reference not set to an instance of an object.

Когда я отладки я могу видеть, что Application.Current равна нулю.

Я проделал несколько поисков и нашел несколько примеров использования диспетчера в модуле Test Test Method, и я попытался выполнить некоторые из них, и они предотвращают ошибку, но код в диспетчере никогда не запускается ,

Я не смог найти примеров, когда есть диспетчер, используемый в методе, который вызывает метод тестирования.

Я работаю в .NET 4.5.2 на компьютере с Windows 10.

Любая помощь будет принята с благодарностью.

Спасибо за ваше время.

+0

Взгляните на предоставленный здесь ответ и дайте мне знать, требуется ли больше объяснений. Http://stackoverflow.com/a/38994745/5233410 – Nkosi

+1

Если вы хотите, чтобы ваш класс прошел тестирование на единицу, вы должны следовать регулярным хорошие практики, связанные с тем, что называется инъекцией зависимостей (DI). В этом случае вы не должны использовать WPF-специфический класс (Dispatcher), особенно с помощью специального приложения класса _STAT_ WPF. Вместо этого обработайте Диспетчер как зависимость и введите его в свой класс, а не сам Диспетчер, но некоторый интерфейс, который предоставляет нужные вам методы. Такой интерфейс вы должны создавать и реализовывать сами и обертывать в него диспетчер WPF. Затем в модульном тесте вы просто предоставляете другую, не WPF-реализацию этого IDispatcher. – Evk

ответ

0

Спасибо всем, кто ответил, ваши отзывы были высоко оценены.

Вот что я в конечном итоге делает:

Я создал частную переменную в моем классе UICommsManager:

private SynchronizationContext _MessageHandlerContext = null; 

и инициализировать это в конструкторе UICommsManager. Затем я обновил свои обработчики сообщений использовать новый _MessageHandlerContext вместо грузоотправитель:

public async Task<bool> Set_Listing_Records_ResponseHandler(string responseChannelSuffix, Action<List<AIDataSetListItem>> successHandler, Action<Exception> errorHandler) 
{ 
    // Subscribe to Query Response Channel and Wire up Handler for Query Response 
    await this.ConnectAsync(); 
    return await this.SubscribeTo_QueryResponseChannelAsync(responseChannelSuffix, new FayeMessageHandler(delegate (FayeClient client, FayeMessage message) { 
     _MessageHandlerContext.Post(delegate { 
      try 
      { 
       ... 
      } 
      catch (Exception e) 
      { 
       ... 
      } 
     }, null); 
    })); 
} 

При использовании из пользовательского интерфейса класс UICommsManager получает SynchronizationContext.Current передается в конструктор.

Я обновил свой юнит-тестов, чтобы добавить следующую приватную переменную:

private SynchronizationContext _Context = null; 

И следующий метод, который инициализирует его:

#region Test Class Initialize 

[TestInitialize] 
public void TestInit() 
{ 
    SynchronizationContext.SetSynchronizationContext(new SynchronizationContext()); 
    _Context = SynchronizationContext.Current; 
} 

#endregion 

И тогда UICommsManager получает переменную _Context перешла в это конструктор из метода испытаний.

Теперь он работает в обоих сценариях при вызове WPF и при вызове Unit Test.

0

Правильный современный код никогда не будет использовать Dispatcher. Это связывает вас с конкретным пользовательским интерфейсом и затрудняет модульный тест (как вы обнаружили).

best Подход заключается в том, чтобы неявно захватить контекст и возобновить его, используя await. Например, если вы знаете, что Set_Listing_Records_ResponseHandler всегда будут вызываться из потока пользовательского интерфейса, то вы можете сделать что-то вроде этого:

public async Task<bool> Set_Listing_Records_ResponseHandler(
    string responseChannelSuffix, 
    Action<List<AIDataSetListItem>> successHandler, 
    Action<Exception> errorHandler) 
{ 
    // Subscribe to Query Response Channel and Wire up Handler for Query Response 
    await this.ConnectAsync(); 
    var tcs = new TaskCompletionSource<FayeMessage>(); 
    await this.SubscribeTo_QueryResponseChannelAsync(responseChannelSuffix, new FayeMessageHandler(
     (client, message) => tcs.TrySetResult(message))); 
    var message = await tcs.Task; 
    // We are already back on the UI thread here; no need for Dispatcher. 
    try 
    { 
    ... 
    } 
    catch (Exception e) 
    { 
    ... 
    } 
} 

Однако, если вы имеете дело с родом событий системы, когда уведомление может неожиданно появляются, то вы не всегда можете использовать неявный захват await. В этом случае подход next best заключается в том, чтобы зафиксировать текущий SynchronizationContext в какой-то момент (например, строительство объекта) и поставить очередь на вашу работу, а не непосредственно диспетчеру. НАПРИМЕР,

private readonly SynchronizationContext _context; 
public Constructor() // Called from UI thread 
{ 
    _context = SynchronizationContext.Current; 
} 
public async Task<bool> Set_Listing_Records_ResponseHandler(
    string responseChannelSuffix, 
    Action<List<AIDataSetListItem>> successHandler, 
    Action<Exception> errorHandler) 
{ 
    // Subscribe to Query Response Channel and Wire up Handler for Query Response 
    await this.ConnectAsync(); 
    return await this.SubscribeTo_QueryResponseChannelAsync(responseChannelSuffix, new FayeMessageHandler(delegate (FayeClient client, FayeMessage message) { 
    _context.Post(new SendOrPostCallback(() => 
    { 
     try 
     { 
      ... 
     } 
     catch (Exception e) 
     { 
      ... 
     } 
    }, null)); 
    })); 
} 

Однако, если вы чувствуете, что вы должны использовать диспетчер (или просто не хочет, чтобы очистить код прямо сейчас), вы можете использовать WpfContext, который обеспечивает Dispatcher реализации для модульного тестирования. Обратите внимание, что вы все еще не можете использовать Application.Current.Dispatcher (если вы не используете MSFakes) - вам нужно будет захватить диспетчера в какой-то момент.

 Смежные вопросы

  • Нет связанных вопросов^_^