2016-11-26 9 views
0

WPF 응용 프로그램의 UI와 비동기 메시징 시스템의 UI 사이에있는 C# 클래스를 작성했습니다. 나는 이제이 클래스에 대한 단위 테스트를 작성하고 운영자와 관련된 문제에 직면하고 있습니다. 다음 메서드는 테스트 할 클래스에 속하며 구독 처리자를 만듭니다. 그래서 나는 Unit Test - Test Method에서이 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가 null 인 것을 알 수 있습니다 디버깅 할 때.

유닛 테스트 - 테스트 메소드에서 디스패처를 사용하는 몇 가지 예가 있었는데이 중 일부를 시도했지만 오류를 방지했지만 디스패처의 코드는 실행되지 않습니다. .

테스트 메소드가 호출하는 메소드에 사용 된 Dispatcher가있는 예제를 찾을 수 없었습니다.

저는 Windows 10 컴퓨터에서 .NET 4.5.2로 작업하고 있습니다.

도움을 주시면 감사하겠습니다.

감사합니다.

+0

자세한 설명이 필요하면 여기에 제공된 대답을 살펴보고 알려주십시오. http://stackoverflow.com/a/38994745/5233410 – Nkosi

+1

수업을 단위 테스트 할 수있게하려면 일반 의존성 주입 (DI)이라고 불리는 것에 관련된 좋은 사례. 이 경우 WPF 관련 클래스 (Dispatcher)를 사용하면 안됩니다. 특히 _static_ class WPF 응용 프로그램을 통해 _ 특히 _ 사용해야합니다. 대신, Dispatcher를 종속으로 처리하고 Dispatcher 자체가 아닌 클래스에 주입하십시오. 그러나 필요한 메소드를 제공하는 일부 인터페이스. 이러한 인터페이스를 작성하고 구현하고 WPF Dispatcher를 랩핑해야합니다. 그런 다음 단위 테스트에서이 IDispatcher의 비 WPF 구현을 제공합니다. – Evk

답변

0

모든 사람들의 반응 덕분에 귀하의 의견은 높이 평가되었습니다.

내가 내 UICommsManager 클래스에서 개인 변수를 만들어 :

private SynchronizationContext _MessageHandlerContext = null; 

을하고 UICommsManager의 생성자이 초기화 여기

는 내가하고 결국 것입니다. 그때 대신 디스패처의 새로운 _MessageHandlerContext를 사용하여 내 메시지 핸들러를 업데이트하십시오 UICommsManager 클래스 생성자에 전달 SynchronizationContext.Current을 가져옵니다 UI에서 사용

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

.

나는 다음과 같은 개인 변수를 추가하는 내 단위 테스트 업데이트

:

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을 사용하지 않습니다. 특정 UI에 묶어 주므로 단위 테스트가 어렵습니다 (발견 한대로).

접근 방식은 await을 사용하여 암시 적으로 컨텍스트를 캡처하고 다시 시작하는 것입니다. 예를 들어, 경우는 다음과 같은 일을 할 수 Set_Listing_Records_ResponseHandler 항상 UI 스레드에서 호출 될 것이라는 점을 알고있다 :

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의 암시 적 캡처를 항상 사용할 수있는 것은 아닙니다. 이 경우 다음으로 가장 좋은 접근 방식은 현재 시점 (예 : 개체 구성)에서 현재 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)); 
    })); 
} 

, 당신이, 당신은 단위 테스트를위한 Dispatcher 구현을 제공하는 WpfContext을 사용할 수 있습니다 (또는 지금의 코드를 정리하고 싶지 않은) 디스패처를 사용해야 느낀다면. MSFakes를 사용하지 않는 한 여전히 Application.Current.Dispatcher을 사용할 수 없다는 점에 유의하십시오. 대신 어떤 시점에서 발송자를 캡처해야합니다.