2009-07-15 75 views
35

我們有一些訪問數據庫的NUnit測試。當其中一個失敗時,它可能會使數據庫處於不一致狀態 - 這不是問題,因爲我們爲每次測試運行重建數據庫 - 但它可能導致其他測試在同一運行中失敗。NUnit - 測試失敗後的清理

是否有可能檢測到其中一個測試失敗並執行某種清理?

我們不想在每個測試中編寫清理代碼,現在我們已經這樣做了。我想在拆解時進行清理,但只有在測試失敗的時候,因爲清理工作可能會很昂貴。

更新:澄清 - 我想測試是簡單的,不包括任何清理或錯誤處理邏輯。我也不想在每次測試運行時執行數據庫重置 - 只有在測試失敗的情況下。而且這個代碼應該可以在Teardown方法中執行,但是我不知道有什麼方法可以獲得信息,如果測試我們正在從失敗或成功中拆除。

UPDATE2

 [Test] 
     public void MyFailTest() 
     { 
      throw new InvalidOperationException(); 
     } 

     [Test] 
     public void MySuccessTest() 
     { 
      Assert.That(true, Is.True); 
     } 

     [TearDown] 
     public void CleanUpOnError() 
     { 
      if (HasLastTestFailed()) CleanUpDatabase(); 
     } 

我要找實施HasLastTestFailed的()

+0

如果您不想在每次測試中進行清理,或者每次測試後都不會進行清理。抱歉。 – rein 2009-07-15 14:47:09

回答

20

這個想法讓我感興趣,所以我做了一點挖掘。 NUnit不具備開箱即用的功能,但NUnit提供了一個完整的可擴展性框架。我發現this great article about extending NUnit - 這是一個很好的起點。玩過它之後,我想出了以下解決方案:如果夾具中的某個測試失敗,則將調用裝有自定義CleanupOnError屬性的方法。

這裏的測試看起來像:

[TestFixture] 
    public class NUnitAddinTest 
    { 
    [CleanupOnError] 
    public static void CleanupOnError() 
    { 
     Console.WriteLine("There was an error, cleaning up..."); 
     // perform cleanup logic 
    } 

    [Test] 
    public void Test1_this_test_passes() 
    { 
     Console.WriteLine("Hello from Test1"); 
    } 

    [Test] 
    public void Test2_this_test_fails() 
    { 
     throw new Exception("Test2 failed"); 
    } 

    [Test] 
    public void Test3_this_test_passes() 
    { 
     Console.WriteLine("Hello from Test3"); 
    } 
    } 

其中屬性很簡單:

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] 
    public sealed class CleanupOnErrorAttribute : Attribute 
    { 
    } 

這裏是它是如何從外接程序執行:

public void RunFinished(TestResult result) 
{ 
    if (result.IsFailure) 
    { 
    if (_CurrentFixture != null) 
    { 
     MethodInfo[] methods = Reflect.GetMethodsWithAttribute(_CurrentFixture.FixtureType, 
                  CleanupAttributeFullName, false); 
     if (methods == null || methods.Length == 0) 
     { 
     return; 
     } 

     Reflect.InvokeMethod(methods[0], _CurrentFixture); 
    } 
    } 
} 

但這裏的棘手的部分:插件必須放在NUnit亞軍旁邊的addins目錄中。 Mine被放置在TestDriven的NUnit runner旁邊。NET目錄:

C:\Program Files\TestDriven.NET 2.0\NUnit\addins

(我創建了addins目錄,它是不存在)

編輯另一件事是清理方法必須static

我砍了一個簡單的插件,你可以從my SkyDrive下載源代碼。您必須在適當的位置添加對nunit.framework.dll,nunit.core.dllnunit.core.interfaces.dll的引用。

一些注意事項:屬性類可以放在代碼中的任何位置。我不想將它放在與插件本身相同的程序集中,因爲它引用了兩個Core NUnit程序集,所以我將它放在了不同的程序集中。如果您決定將其放在其他地方,請記住要更改CleanAddin.cs中的行。

希望有所幫助。

1

有關使用try-catch塊,重新拋出異常捕獲什麼?

try 
{ 
//Some assertion 
} 
catch 
{ 
    CleanUpMethod(); 
    throw; 
} 
+0

這不是每個測試都能解釋嗎? 「我們不想在每次測試中都寫清理代碼」 – 2009-07-15 14:53:37

+0

是的,我發佈了第二個解決方案,以更好地解決更新的意見 – 2009-07-15 15:01:28

0

它是如何失敗的?是否有可能把它放在一個嘗試(做測試)/捕獲(修復損壞的數據庫)/ finally塊?

或者你可以調用一個私人方法來解決它,當你檢查你的失敗情況。

2

是的,有。您可以使用Teardown屬性,這將在每次測試後拆除。您想要在每次測試之前和之後應用您擁有的數據庫「重置」腳本並拆卸並重新設置。

這一屬性用於一個 的TestFixture內,以提供一組通用的被 運行每個測試方法之後執行 功能。

更新:根據的評論和更新的問題,我說你可以使用拆卸屬性和使用私有變量,以表明該方法的內容是否應該解僱。

雖然,我也看到你不想要任何複雜的邏輯或錯誤處理代碼。

鑑於此,我認爲標準的Setup/Teardown最適合您。無論是否有錯誤都無關緊要,您也不必擁有任何錯誤處理代碼。

如果您需要特別清理,因爲下一個測試取決於當前測試的成功完成,我建議重新檢查您的測試 - 它們可能不應該依賴於其他測試。

+0

我認爲他知道拆解,他想做的是在拆卸時是否意識到是否有任何測試在夾具失敗的情況下,如果沒有,他可以跳過數據庫重建。 – 2009-07-15 14:41:34

+0

那麼,可以生成測試數據以獨立於待測試的測試,但要爲每個測試設置數據庫需要更長的時間,然後在測試中編寫清理代碼。 我的目標是減少構建時間和清理設置/拆卸代碼。不,嘲笑不是這些測試的選項,我們已經在我們的單元測試中使用它們... – bh213 2009-07-15 14:55:23

+0

我明白你的困境,但我沒有任何進一步的想法.... – 2009-07-15 15:01:28

2

雖然可能會強制nUnit這樣做,但它不是最明智的設計,您可以隨時在某處設置臨時文件,如果該文件存在,請運行清理。

我建議更改代碼,以便啓用數據庫事務並在測試結束時將數據庫簡單恢復到原始狀態(例如,放棄表示您的單元測試的事務)。

1

我會像現在這樣做phsr建議,當你能負擔得起,重構測試,以便他們永遠不必依賴於另一個測試需要的相同數據,或者甚至更好地抽象數據訪問層並嘲笑結果從那個數據庫。這聽起來像是你的測試是相當昂貴的,你應該對數據庫中的數據庫和業務邏輯執行所有的查詢邏輯,你並不關心返回的結果是什麼。

您還可以更好地測試您的ExceptionHandling。

1

另一個選擇是有一個特殊的函數,它會拋出你的異常,在testfixture中設置一個表示發生異常的開關。

public abstract class CleanOnErrorFixture 
{ 
    protected bool threwException = false; 

    protected void ThrowException(Exception someException) 
    { 
     threwException = true; 
     throw someException; 
    } 

    protected bool HasTestFailed() 
    { 
      if(threwException) 
      { 
       threwException = false; //So that this is reset after each teardown 
       return true; 
      } 
      return false; 
    } 
} 

然後使用你的例子:

[TestFixture] 
public class SomeFixture : CleanOnErrorFixture 
{ 
    [Test] 
    public void MyFailTest() 
    { 
     ThrowException(new InvalidOperationException()); 
    } 

    [Test] 
    public void MySuccessTest() 
    { 
     Assert.That(true, Is.True); 
    } 

    [TearDown] 
    public void CleanUpOnError() 
    { 
     if (HasLastTestFailed()) CleanUpDatabase(); 
    } 
} 

這裏唯一的問題是,堆棧跟蹤將導致CleanOnErrorFixture

1

一個選項沒有提及到目前爲止是包裝測試起來一個TransactionScope對象,因此測試從不向數據庫提交任何內容時會發生什麼並不重要。

這是some details on the technique。如果你在單元測試和交易範圍上進行搜索,你可能會發現更多(儘管如果你打了一個數據庫你真的在做集成測試)。我過去成功地使用了它。

這種方法很簡單,不需要任何清理,並確保測試是孤立的。

編輯 - 我剛纔注意到雷海耶斯的回答也類似於我的。

0

我不是說這是一個好主意,但它應該起作用。請記住,斷言失敗只是例外。另外,不要忘記還有一個[TestFixtureTearDown]屬性在夾具中的所有測試都運行後只運行一次。

使用這兩個事實,你可以寫一些東西,如設置一個標誌,如果一個測試失敗,並檢查測試夾具中的標誌的值拆除。

我不推薦這個,但它會工作。你並沒有真正使用NUnit,但你可以做到。


[TestFixture] 
public class Tests { 
    private bool testsFailed = false; 

    [Test] 
    public void ATest() { 
     try { 
      DoSomething(); 
      Assert.AreEqual(....); 
     } catch { 
      testFailed = true; 
     } 
    } 

    [TestFixtureTearDown] 
    public void CleanUp() { 
      if (testsFailed) { 
       DoCleanup(); 
      } 
    } 
} 
60

從版本2.5.7開始,NUnit允許Teardown檢測上次測試是否失敗。 新的TestContext類允許測試訪問關於它們自己的信息,包括TestStauts。

欲瞭解更多詳情,請參閱http://nunit.org/?p=releaseNotes&r=2.5.7

[TearDown] 
public void TearDown() 
{ 
    if (TestContext.CurrentContext.Result.Status == TestStatus.Failed) 
    { 
     PerformCleanUpFromTest(); 
    } 
} 
0

您可以
if (TestContext.CurrentContext.Result.Status != TestStatus.Passed)
一些代碼,如果測試失敗要執行添加[TearDown]方法。