簡體   English   中英

單元測試和編碼設計

[英]Unit Testing and Coding Design

在很多TDD教程中,我看到這樣的代碼:

public class MyClass
{
    public void DoSomething(string Data)
    {
      if (String.IsNullOrWhiteSpace(Data))
        throw new NullReferenceException();
    }
}


[Test]
public DoSomething_NullParameter_ThrowsException()
{
   var logic = MyClass();

   Assert.Throws<NullReferenceException>(() => logic.DoSomething(null));
}

這一切都有意義,但在某些時候你將會到達實際使用MyClass的類,並且你想測試異常是否被處理:

public class EntryPointClass
{
  public void DoIt(string Data)
  {
     var logicClass = new MyClass();

     try
     {
        logicClass.DoSomething(Data);
     }
     catch(NullReferenceException ex)
     {

     }
  }
}

[Test]
public DoIt_NullParameter_IsHandled()
{
  var logic = new EntryPointClass()

  try
  {
    logic.DoIt(null);
  }
  catch
  {
    Assert.Fail();
  }
}

那么為什么不把my / class中的try / catch放在首先,而不是拋出異常,並在MyClass單元測試類中測試為null,而不是在EntryPointClass單元測試類中?

通常,您的異常處理將如下所示:

public class EntryPointClass
{
  Logger _logfile;
  // ...
  public void DoIt(string Data)
  {
     var logicClass = new MyClass();

     try
     {
        logicClass.DoSomething(Data);
     }
     catch(NullReferenceException ex)
     {
         _logfile.WriteLine("null reference exception occured in method 'DoIt'");
     }
  }
}

Logger只是您在MyClass所在地不能使用的正確異常處理所需資源的示例。您還可以在此處添加消息框的顯示或類似內容。)

MyClass級別,您通常沒有適當的工具可用於正確的異常處理(並且您不希望在那里添加它們以保持類與特定的日志記錄機制分離)。

請注意,此設計決策與執行TDD無關。 如果你希望你的類MyClass實際上自己捕獲異常,你必須以不同的方式編寫測試,這是正確的。 但是,如果捕獲異常並不會阻止您的自動測試,那么這只是一個好主意。 例如,當MyClass顯示硬編碼警告對話框時,請嘗試為MyClass編寫一個好的單元測試。

當然,上面的例子表明,當您首次需要像記錄器這樣的東西來運行時,單元測試EntryPointClass可能會變得更難。 通常,您可以通過使用ILogger接口在構造時提供記錄器來解決此問題,該接口允許使用mock替換記錄器。 或者在這個簡單的情況下,可能根本不為單元測試初始化​​記錄器並將其編碼為:

 public class EntryPointClass 
 {
      // ....
      catch(NullReferenceException ex)
      {
          if(_logfile!=null)
              _logfile.WriteLine("null reference exception occured in method 'DoIt'");
      }    
      // ....
 }

例外表示代碼的異常情況(請參閱MSDN 異常處理指南)。 如果參數null對於DoSomething是例外,那么拋出它就會非常有意義,無論以后 如何使用該類以及如何使用該類。 值得強調的是Exception投擲部分正在精確描述它:

通過拋出異常來報告執行失敗。 如果成員無法成功執行其設計要執行的操作,則應將其視為執行失敗並應拋出異常

更不用說,在寫DoSomething你可能不知道會使用什么代碼(實際上你不應該知道事件,也不關心)。

因此,如果異常拋出是代碼合同的一部分 - 它應該進行測試,並且應該作為DoSomething測試的一部分進行測試。

為什么不把try / catch放在MyClass中呢?

例外是用於解耦錯誤檢測和錯誤處理。 如果無法在MyClass中本地處理錯誤(在所有情況下),那么您應該拋出異常。

這允許本地錯誤檢測(MyClass)和任何捕獲異常的人的錯誤處理。


如果方法DoIt是空安全的,則在EntryPointClass進行測試。 它與MyClass的使用幾乎沒有關系。

換句話說: EntryPointClass中的測試不會和(對於代碼穩定性和封裝問題)不應該依賴於使用MyClass的事實。

單元測試測試工作單元

編寫一個測試異常的測試,然后將代碼寫入該規范並觀察測試通過(這是您的第一個示例)

然后編寫處理異常的代碼(如果可能的話),如果這樣做,那么測試不應該考慮該異常,因為你已經處理了它,你應該測量該方法的輸出。

如果您無法處理異常,則應省略catch步驟(除非您在此階段進行日志記錄)並在此情況下讓它上升到堆棧,您再次斷言該方法需要異常。

你永遠不會在你的測試代碼中放置一個try catch,你只斷言是否拋出異常。

正如您正在測試您班級的正確功能。

您實際上正在測試兩種非常不同的條件:

  1. 測試MyClass在參數無效時拋出異常
  2. 測試作為MyClass用戶的EntryPointClass如何處理MyClass拋出的異常

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM