[英]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,你只斷言是否拋出異常。
正如您正在測試您班級的正確功能。
您實際上正在測試兩種非常不同的條件:
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.