2011-01-24 40 views
12

我在尊敬的John Skeet先生的指示下重新發布了這個問題,他建議我設計一個簡單的測試程序來隔離並演示我遇到的問題並重新發布問題。這個問題從this one開始,所以請原諒我,如果這聽起來很熟悉。你可能會從這個問題收集關於這個問題的額外細節。NUnit:爲什麼不Assert.Throws <T> Catch My ArgumentNullException?

我遇到的問題來自NUnit 2.5.9的Assert.Throws<T>。有時,它將無法捕獲由TestDelegate調用的方法中拋出的任何異常。我已經在下面的代碼中以可重現的方式固定了這種行爲。 (雖然這可能,誠然,是無法在我的機器的情況下™

重現錯誤,我已經創建了兩個C#DLL項目的解決方案:

  • 第一個包含一類,這個方法是一個擴展方法,它封裝了創建一個SqlCommand所需的邏輯,填充它的參數並調用ExecuteScalar就可以了,這個項目不包含其他的參考信息
  • 第二個包含一個具有兩個方法的類它測試第一個DLL中的方法是否按預期工作,該項目引用第一個,幷包含ar參考NUnit框架。沒有其他程序集被引用。

當我通過在調試器中測試步驟,我觀察到以下:

  1. Assert.Throws正確調用ExecuteScalar<T>擴展方法。
  2. 參數值爲空,如預期。
  3. ExecuteScalar<T>測試其空值參數。
  4. 調試器確實命中並執行包含throw new ArgumentNullException(...)的行。
  5. 執行throw之後,應用程序的控制權不會立即轉移到Assert.Throws。相反,它繼續在ExecuteScalar<T>的下一行。
  6. 只要下一行代碼執行,調試程序就會中斷,並顯示錯誤「參數null異常未被用戶代碼處理」。

隔離此行爲的源代碼如下所示。

可拓方法

namespace NUnit_Anomaly 
{ 
    using System; 
    using System.Data; 
    using System.Data.SqlClient; 

    public static class Class1 
    { 
     public static T ExecuteScalar<T>(this SqlConnection connection, string sql) 
     { 
      if (connection == null) 
      { 
       throw new ArgumentNullException("connection"); 
      } 

      if (sql == null) 
      { 
       throw new ArgumentNullException("sql"); 
      } 

      using (var command = connection.CreateCommand()) 
      { 
       command.CommandType = CommandType.Text; 
       command.CommandText = sql; 
       return (T)command.ExecuteScalar(); 
      } 
     } 
    } 
} 

測試用例

namespace NUnit_Tests 
{ 
    using System; 
    using System.Data.SqlClient; 
    using System.Diagnostics; 

    using NUnit.Framework; 

    using NUnit_Anomaly; 

    [TestFixture] 
    public class NUnitAnomalyTest 
    { 

     [Test] 
     public void ExecuteDataSetThrowsForNullConnection() 
     { 
      Assert.Throws<ArgumentNullException>(() => ((SqlConnection)null).ExecuteScalar<int>(null)); 
     } 

     [Test] 
     public void ExecuteDataSetThrowsForNullSql() 
     { 

      const string server = "MY-LOCAL-SQL-SERVER"; 
      const string instance = "staging"; 
      string connectionString = String.Format("Data Source={0};Initial Catalog={1};Integrated Security=True;", 
                server, 
                instance); 

      using (var connection = new SqlConnection(connectionString)) 
      { 
       Assert.Throws<ArgumentNullException>(() => connection.ExecuteScalar<int>(null)); 
      } 
     } 
    } 
} 

的淨效應是,測試失敗時,他們不應該。據我所知,Assert.Throws<T>應該趕上我的例外,並通過測試。

UPDATE

我把漢斯的諮詢和檢查例外對話框。我沒有打破拋出的例外,但我打破了未處理的用戶例外。顯然,這就是爲什麼當引發異常時調試器進入IDE。清除複選框解決了問題,並Assert.Throws<T>拿起它。但是,如果我沒有這樣做,我不能只按F5繼續執行,或者異常將變爲NullReferenceException

所以現在的問題是:我可以在每個項目的基礎上配置異常中斷嗎?我只想在測試時這樣做,但不是一般。

+1

調試+異常,確保投擲複選框關閉。 – 2011-01-24 18:21:30

+0

@Hans Passant:我已經確認沒有選中Thrown複選框。 – 2011-01-24 18:32:59

回答

15

實際情況是什麼Assert.Throws確實抓住你的例外,但是Visual Studio中的第一次機會異常停止反正。你可以通過按F5來檢查它; Visual Studio會很高興地繼續執行。

正如例外助手告訴你的,例外情況是用戶代碼未處理。所以我們知道Visual Studio由於某種原因不認爲NUnit是用戶代碼。

enter image description here

的Visual Studio實際上告訴了你這個純文本,如果你知道去哪裏找:

enter image description here

還有在堆棧跟蹤這個事實的證據:

enter image description here

解決方案1 ​​:在調試符號中使用NUnit的調試版本。那will get Visual Studio to regard NUnit as user code,並因此停止將您的異常視爲「未經用戶代碼處理」。這不是微不足道的,但長期來看可能會更好。

解決方案2:關閉Visual Studio中的調試設置中的 「啓用僅我的代碼」 複選框:

enter image description here

附:我並未考慮避免全部使用Assert.Throws<T>的解決方法,但當然有這樣做的方法。

相關問題