Как проверить исключения в асинхронных методах - PullRequest
1 голос
/ 03 марта 2012

Я играю с Visual Studio 11 Beta.

Учитывая этот код:

namespace KC.DataAccess.Global
{
    /// <summary>Global methods for SQL access</summary>
    public static class SQL
    {    
        public async static void ExecuteNonQuery(string ConnStr, string Query)
        {
            if (string.IsNullOrEmpty(ConnStr)) throw new ArgumentNullException("ConnStr");
            if (string.IsNullOrEmpty(Query)) throw new ArgumentNullException("Query");
            SqlConnection conn = new SqlConnection(ConnStr);
            SqlCommand cmd = PrepSqlConnection(ref conn, Query);
            Exception exc = null;
            for (int i = 0; i < 3; i++)
                try { await Task.Run(() => cmd.ExecuteNonQuery()); break; }
                catch (Exception ex) { Thread.Sleep(50); exc = ex; }
            if (exc != null) throw new ApplicationException("Command failed after maximum attempts", exc);
            conn.Close();
            conn.Dispose();
         }
    } 
} 

Поскольку это асинхронный метод, исключения, похоже, не передаются вызывающему методу. У меня есть тестовые случаи, которые поэтому терпят неудачу:

using Target = KC.DataAccess.Global.SQL;
[TestMethod]
[TestCategory("Unit")]
[ExpectedException(typeof(ArgumentNullException))]
public void ExecuteNonQueryFail1()
{
    Target.ExecuteNonQuery(null, "select 1");
}

Часть проверки ExecuteNonQuery явно выдает исключение в этом случае, и я вижу, что это происходит при отладке.

Я изменил метод теста на асинхронный и синтаксис на await Task.Run (() => Target.ExecuteNonQuery ()) , безрезультатно.

Вопросы:

  • Выполняет ли ExecuteNonQuery исключение вообще?
  • Почему ExecuteNonQueryFail1 не видит исключение?
  • Как я могу изменить метод тестирования или сам метод, чтобы правильно обработать исключение и пройти тестовый пример, не отказываясь от асинхронной природы метода?

Ответы [ 2 ]

4 голосов
/ 04 марта 2012

VS11 Beta имеет первоклассную поддержку для методов тестирования, которые возвращают Task.

Так что если вы измените свою подпись на:

public async static Task ExecuteNonQuery(string ConnStr, string Query)

, то вы можете проверить ее следующим образом:

using Target = KC.DataAccess.Global.SQL;
[TestMethod]
[TestCategory("Unit")]
[ExpectedException(typeof(ArgumentNullException))]
public async Task ExecuteNonQueryFail1()
{
  await Target.ExecuteNonQuery(null, "select 1");
}

(у меня не было возможности самостоятельно опробовать эту новую поддержку, но я читал, что она должна работать).

Примечание: вы должны вернуть Task в ваших async методах, если вы не должны вернуть void (например, для обработчика событий).Task ожидаемо, поэтому код более пригоден для повторного использования.Я расскажу об этом в моем вступительном блоге "Async and Await" .

Если вам по какой-то причине нужно протестировать метод async void, вам нужно предоставить свой SynchronizationContextчтобы поймать любые исключения (см. раздел «Асинхронное модульное тестирование» недавнего сообщения Стивена Туба в блоге ).

У меня есть пара постов в блоге, посвященных асинхронному модульному тестированию ( часть 1 , часть 2 ).Я написал базовый проект Async Unit Tests для VS2010 + AsyncCTP или VS11-DevPreview, но у меня еще не было возможности протестировать его с VS11-Beta.Это был бы самый простой способ для юнит-тестирования async void методов, если вам нужно. [CodePlex | NuGet]

3 голосов
/ 03 марта 2012

Поскольку ваш метод возвращает void, исключение не может распространиться на вызывающий код. Если вы измените тип возвращаемого значения на Task, теперь вы можете наблюдать исключение, но вы должны сделать это явно.

Я думаю, что лучший способ изменить ваш код вызова - просто позвонить Wait(). Если код в Task сгенерировал исключение, Wait() сгенерирует AggregateException, который будет содержать исходное исключение.

...