2013-11-03 1 views
10

Я довольно знаком с шаблоном async/await, но я натыкаюсь на какое-то поведение, которое поражает меня как нечетное. Я уверен, что есть вполне обоснованная причина, почему это происходит, и я хотел бы понять поведение.Неожиданное поведение при передаче async Действия около

Фон в том, что я разрабатываю приложение для Windows Store, а так как я осторожный, добросовестный разработчик, я все тестирую. Я довольно быстро обнаружил, что ExpectedExceptionAttribute не существует для WSA. Странно, правда? Ну, не проблема! Я могу больше или меньше реплицировать поведение с помощью метода расширения! Поэтому я написал следующее:

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. Я угадываю, потому что, насколько касается времени выполнения, это не a Action, я просто забиваю лямбду в нее. Я знаю, что лямбда с радостью будет назначена либо Action, либо Func<Task>.

+3

В тестах Windows Store используйте 'Assert.ThrowsException', который ([по версии VS2012 Update 2] (http://support.microsoft.com/kb/2797912)) поддерживает 'async' lambdas. Обратите внимание, что 'Action' является * синхронным * методом без возвращаемого значения, а' Func 'является * асинхронным * методом без возвращаемого значения. –

+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, но это может привести к сбою вашего тестера позже, когда будет завершена такая операция aync и будет выбрана LoginFailedException. Я не уверен, что представляет собой контекст синхронизации среды выполнения единичного теста, но если он использует значение по умолчанию SynchronizationContext, в этом случае исключение действительно может быть выбрано ненаблюдаемым в другом потоке.

VS2012 автоматически поддерживает асинхронные модульные тесты, если сигнатура метода тестирования async Task. Итак, я думаю, вы ответили на свой вопрос, используя await и Func<T> для вашего теста.

+0

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

+0

@ geezer498, я верю, что решение OP, где он делает 'try {await a(); } catch ... 'правильно. – Noseratio

+1

+1. FYI, MSTest использует планировщик задач по умолчанию (поток пула), поэтому исключение генерируется в потоке пула потоков, возможно, сбой тестового бегуна. Я говорю «возможно», потому что технически существует условие гонки между тестируемым бегуном и исключением; например, если исключение было отложено, тестовый бегун может фактически завершить до того, как он разбился. –

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

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