2013-11-03 5 views
10

비동기/대기 패턴에 익숙하지만 비정상적인 동작이 발생합니다. 내가 왜 그런 일이 일어나고 있는지 완벽하게 타당한 이유가 있으며, 그 행동을 이해하고 싶습니다.비동기 전달시 예기치 않은 동작이 발생했습니다.

여기 배경은 내가 Windows Store 앱을 개발하고 있으며, 나는 신중하고 양심적 인 개발자이기 때문에 모든 것을 테스트하고있다. 꽤 빨리 WS35에 ExpectedExceptionAttribute이 존재하지 않는다는 것을 발견했습니다. 이상하지, 그렇지? 글쎄, 아무 문제 없어! 확장 메서드를 사용하여 동작을 더 많이 또는 덜 복제 할 수 있습니다! 그래서 나는 이것을 썼다 :

public static class TestHelpers 
{ 
    // There's no ExpectedExceptionAttribute for Windows Store apps! Why must Microsoft make my life so hard?! 
    public static void AssertThrowsExpectedException<T>(this Action a) where T : Exception 
    { 
     try 
     { 
      a(); 
     } 
     catch (T) 
     { 
      return; 
     } 

     Assert.Fail("The expected exception was not thrown"); 
    } 
} 

그리고 아름답게 작동한다.

그래서 내가 확신하고 싶은 비동기 메서드를 누르기 전까지 단위 테스트를 행복하게 계속 작성하여 특정 상황에서 예외가 발생했습니다. "문제 없습니다."나는 비공식 람다를 전달할 수 있다고 생각했습니다.

[TestMethod] 
public async Task Network_Interface_Being_Unavailable_Throws_Exception() 
{ 
    var webManager = new FakeWebManager 
    { 
     IsNetworkAvailable = false 
    }; 

    var am = new AuthenticationManager(webManager); 
    Action authenticate = async() => await am.Authenticate("foo", "bar"); 
    authenticate.AssertThrowsExpectedException<LoginFailedException>(); 
} 

이 놀랍게도, 런타임 오류가 발생합니다 :

그래서 나는이 시험 방법을 썼다. 실제로는 이 충돌합니다. 테스트 러너!

나는 나의 AssertThrowsExpectedException 방법의 과부하를 만든 :

public static async Task AssertThrowsExpectedException<TException>(this Func<Task> a) where TException : Exception 
{ 
    try 
    { 
     await a(); 
    } 
    catch (TException) 
    { 
     return; 
    } 

    Assert.Fail("The expected exception was not thrown"); 
} 

나는 내 테스트를 쥐게 : 모든가는 이유

[TestMethod] 
public async Task Network_Interface_Being_Unavailable_Throws_Exception() 
{ 
    var webManager = new FakeWebManager 
    { 
     IsNetworkAvailable = false 
    }; 

    var am = new AuthenticationManager(webManager); 
    Func<Task> authenticate = async() => await am.Authenticate("foo", "bar"); 
    await authenticate.AssertThrowsExpectedException<LoginFailedException>(); 
} 

내 솔루션 괜찮아, 난 그냥 정확히 궁금하네요 비동기 Action을 호출하려고하면 배 모양입니다. 런타임과 관련하여 이 아니기 때문에Action이 아니므로, 나는 그 안에 람다를 넣었습니다. 나는 람다가 행복하게 Action 또는 Func<Task>에 할당된다는 것을 알고 있습니다.

+3

Windows 테스트에서 'Assert'를 사용하십시오.(as VS2012 Update 2 (http://support.microsoft.com/kb/2797912))는'async' lambdas를 지원합니다. 'Func '은 반환 값이없는 * 비동기 * 메소드 인 반면,'Action'은 반환 값이없는 * synchronous * 메소드입니다. –

+0

오, 나는 Assert.ThrowsException을 알지 못했다. 나는 그것을 사용하기 위해 나의 테스트를 바꿀 것이다. –

답변

6

는 두 번째 코드 조각 시나리오에서 테스터를 충돌 할 수 있습니다 것은 놀라운 일이 아니다 :

Action authenticate = async() => await am.Authenticate("foo", "bar"); 
authenticate.AssertThrowsExpectedException<LoginFailedException>(); 

그것은 사실이다 화재 - 및 - 잊지 당신이 호출 할 때 작업 an async void method의 호출 :

try 
{ 
    a(); 
} 

a()은 즉시 반환하고 AssertThrowsExpectedException 방법도 제공합니다. 동시에 am.Authenticate 내부에서 시작된 일부 활동이 백그라운드에서 계속 실행될 수 있으며 풀 스레드에서 계속 실행될 수 있습니다. 정확히 무슨 일이 일어나고 있는지는 am.Authenticate의 구현에 따라 다르지만 나중에 비동기 작업이 완료되고 나중에 LoginFailedException을 throw하면 테스터가 중단 될 수 있습니다. 유닛 테스트 실행 환경의 동기화 컨텍스트가 무엇인지 확실하지 않지만, 기본값 인 SynchronizationContext을 사용하는 경우 예외가 실제로이 경우 다른 스레드에서 관찰되지 않을 수 있습니다.

VS2012는 테스트 메소드 서명이 async Task 인 경우 비동기 단위 테스트를 자동으로 지원합니다. 따라서 귀하의 질문에 awaitFunc<T>을 사용하여 답변 해 주셨습니다.

+0

또한 테스트중인 비동기 메서드에서 메서드가 호출 된 스레드에서 오류를 throw해야합니다. 그렇지 않으면 호출자가 catch 할 수 없으며 솔루션에서도 런타임 예외가 발생합니다. –

+0

@ geezer498, 나는 OP의 해결책이 어디에서'시도 {await a(); } catch ...'올바른 것입니다. – Noseratio

+1

+1. 참고로 MSTest는 기본 (스레드 풀) 작업 스케줄러를 사용하므로 스레드 풀 스레드에서 예외가 throw되어 테스트 러너가 충돌 할 수 있습니다. 기술적으로 시험 주자와 예외 사이에 경쟁 조건이 있기 때문에 "아마도"라고 말합니다. 예를 들어, 예외가 지연된 경우 테스트 러너는 실제로 충돌하기 전에 완료 할 수 있습니다. –

관련 문제