2012-03-03 59 views
1

我在玩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的验证部分显然在这种情况下抛出一个异常,我看到它扔在我调试。

我已将测试方法更改为异步并且语法为等待Task.Run(()=> Target.ExecuteNonQuery()),无济于事。

问题:

  • 是的ExecuteNonQuery抛出异常呢?
  • 为什么ExecuteNonQueryFail1看不到异常?
  • 如何更改测试方法或方法本身,以正确处理异常并通过测试用例,而不会放弃方法的异步性质?
+0

注意当我改变目标方法返回一个任务发生此相同的行为,当我能够在测试的配置文件ThrowUnobservedTaskExceptions。 – tsilb 2012-03-03 06:11:59

+1

您应该使用'TaskEx.Delay'而不是'Thread.Sleep'。 – 2012-03-04 01:40:20

回答

2

由于您的方法返回void,现在有办法如何将异常传播到调用代码。如果将返回类型更改为Task,则现在可以观察异常,但必须明确执行该操作。

我认为修改您的呼叫代码的最佳方式是拨打Wait()。如果Task中的代码抛出异常,则Wait()将抛出将包含原始异常的AggregateException

+0

如果你使用'Wait',那么你就不能使用'ExpectedException'。使测试方法“异步任务”并让它“等待”“任务”会更清洁。 (这是在VS11Beta中添加的新功能;它不在VS11DevPreview中)。 – 2012-03-03 22:13:22

+0

你可以使用'[的ExpectedException(typeof运算(AggregateException))]',但我想这是不够具体。 – svick 2012-03-03 22:18:29

+0

我有一个过载任务/ AWAIT,但也有可以从字面上做任何一段SQL调用。这一个是无效的,因为我希望能够'开火并忘记',就像错误记录或将作业提交到队列中一样。 – tsilb 2012-03-04 00:14:41

4

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

(我还没有机会尝试这个新的支持自己,但我读过这应该工作)。

注:应该async方法返回Task除非你必须返回void(例如,对于事件处理程序)。 Task是可以等待的,所以代码更加可重用。我在my "Async and Await" intro blog post中介绍过。

如果你需要测试的async void方法出于某种原因,你需要提供自己的SynchronizationContext捕捉任何异常(见Stephen Toub's recent blog post的“异步单元测试”部分)。

我有几个专门处理异步单元测试的博文(part 1,part 2)。我写了VS2010 + AsyncCTP或VS11-DevPreview基本异步单元测试项目,但我还没有机会与VS11-β测试它。如果需要的话,这将是最简单的方法来测试async void方法。 [CodePlex | NuGet]

+0

你的逻辑是健全的,但它仍然没有抛出。见svick的答案。 – tsilb 2012-03-04 00:29:00

+0

你真的改变'Target.ExecuteNonQuery'为'Task'的返回类型?用'Task.Run'包装它根本不是一回事。 – 2012-03-04 01:36:45

+0

是的,我将它更改为任务。我正在寻找的所有方法都是测试它,检测异常,并且能够在不在别处等待的情况下运行它。很明显,我真的想等待测试。 – tsilb 2012-03-04 13:09:38