2014-09-03 38 views
6

Microsoft.VisualStudio.TestTools.UnitTesting命名空間中,有便利的靜態類Assert來處理測試中正在進行的斷言。Microsoft.VisualStudio.TestTools.UnitTesting.Assert通用方法重載行爲

讓我感到困惑的是大多數方法都極其重載,最重要的是,它們有一個通用版本。一個具體的例子是Assert.AreEqual其中有18個重載,其中:

Assert.AreEqual<T>(T t1, T t2) 

什麼用這種泛型方法嗎?最初,我認爲這是一種直接調用IEquatable<T> Equals(T t)方法的方法,但事實並非如此;它總是會調用非通用版本object.Equals(object other)。在編寫了相當多的單元測試之後,我發現了這個難題,而不是像預期的那樣事先檢查類的定義。

爲了調用Equals的通用版本,被定義的一般方法將不得不爲:

Assert.AreEqual<T>(T t1, T t2) where T: IEquatable<T> 

有一個很好的理由,爲什麼它沒有這樣做呢?

是的,你失去的一般方法爲所有那些沒有實現IEquatable<T>類型,但無論如何,它不是一個很大的損失,因爲平等將通過object.Equals(object other)進行檢查,所以Assert.AreEqual(object o1, object o2)已經足夠好了。

目前的通用方法是否提供了我不考慮的優點,還是僅僅是因爲沒有人會停下來思考它,因爲這不是什麼大問題?我看到的唯一優勢是參數類型安全,但看起來有點差。

編輯:修正了一個錯誤,我一直在提及IComparable,我的意思是IEquatable

+0

你不能約束它,因爲任何沒有它自己的重載的T自然會解析爲重載解析的一部分,只有這樣編譯器纔會抱怨任何沒有實現接口的T的未實現約束。至於你的錯誤假設,你是否在構建你的測試策略之前檢查了方法的文檔? – 2014-09-03 16:18:22

+0

AnthonyPegram:如果你約束它,你不能傳入任何沒有實現這個接口的T,那麼在運行時如何重載解析失敗呢? – InBetween 2014-09-03 16:38:03

+1

它會在編譯時失敗,但重載解析已經決定了T超載,然後*然後*檢查約束(和失敗)。約束不是簽名的一部分。如果你搜索這個短語,你會發現很多資源(這裏和其他地方,尤其是Eric Lippert的博客),這會給你更多的細節。 – 2014-09-03 16:40:07

回答

4

由於constraints not being part of the signature經常遇到的問題,具有該約束的方法將是非理想的。

問題在於,對於任何沒有被自己的特定超載覆蓋的T,編譯器會選擇通用的AreEqual<T>方法作爲重載解析期間的最佳擬合,因爲它確實通過完全匹配。在該過程的另一個步驟中,編譯器會評估T是否通過該約束。對於任何不執行IEquatable<T>的T,此檢查將失敗,代碼將無法編譯。

考慮單元測試庫代碼的這個簡化的例子,可能在庫中存在的類:

public static class Assert 
{ 
    public static void AreEqual(object expected, object actual) { } 
    public static void AreEqual<T>(T expected, T actual) where T : IEquatable<T> { } 
} 

class Bar { } 

類BAR沒有實現在約束的接口。如果我們再下面的代碼添加到一個單元測試

Assert.AreEqual(new Bar(), new Bar()); 

的代碼將失敗,因爲上是最佳人選方法不滿意的約束進行編譯。 (Bar替代T,這使得它比object更好的候選者。)

類型「欄」不能在通用類型或方法被用作類型參數「T」「Assert.AreEqual <Ť >(T,T)'。沒有從'Bar'到'System.IEquatable <Bar>'的隱式引用轉換。

爲了滿足編譯器和允許我們的單元測試代碼編譯和運行,我們將必須投至少一個輸入到所述方法object使得非通用的過載可被選擇,這對於的任何都是正確的,因爲T可能存在於您自己的代碼中或您使用的代碼中,您希望在未實現該接口的測試用例中使用該代碼或代碼。

Assert.AreEqual((object)new Bar(), new Bar()); 

所以這個問題一定要問 - 這是理想的嗎?如果你正在編寫一個單元測試庫,你會創建一個這樣一個不友好的限制嗎?我懷疑你不會,而且微軟單元測試庫的實現者(無論是否因爲這個原因)都沒有。

+0

+1感謝您讓我明白髮生了什麼。使我想知道爲什麼類型約束不是重載解決方案的一部分......也讓我想知道爲什麼編譯器會拋出一個錯誤,而不是發出警告並回落到'AreEqual(對象預期...「之一。 – 2015-09-10 12:00:49

1

基本上,泛型超載迫使你比較兩個相同類型的對象。如果您更改期望類型或實際值,將出現編譯錯誤。這裏是MSDN blog描述得很好。

+0

是的,這就是我在問題中提到的...唯一的優勢是參數類型安全,但是在這種特殊情況下找到這個理由相當差。 – InBetween 2014-09-03 16:40:19

1

你可以反編譯的方法,並看到所有它確實是增加一個類型檢查(通過ILSpy),它甚至沒有在我看來,做正確(它檢查後平等類型):

public static void AreEqual<T>(T expected, T actual, string message, params object[] parameters) 
{ 
    message = Assert.CreateCompleteMessage(message, parameters); 
    if (!object.Equals(expected, actual)) 
    { 
     string message2; 
     if (actual != null && expected != null && !actual.GetType().Equals(expected.GetType())) 
     { 
      message2 = FrameworkMessages.AreEqualDifferentTypesFailMsg((message == null) ? string.Empty : Assert.ReplaceNulls(message), Assert.ReplaceNulls(expected), expected.GetType().FullName, Assert.ReplaceNulls(actual), actual.GetType().FullName); 
     } 
     else 
     { 
      message2 = FrameworkMessages.AreEqualFailMsg((message == null) ? string.Empty : Assert.ReplaceNulls(message), Assert.ReplaceNulls(expected), Assert.ReplaceNulls(actual)); 
     } 
     Assert.HandleFailure("Assert.AreEqual", message2); 
    } 
} 

理論上,它可以使用EqualityComparer<T>.Default來使用泛型等式(如果可用),但是如果可用的話,它將回退到非泛型等式。這樣就不需要限制IEquatable<T>。對於泛型和非泛型Equals方法具有不同的行爲是代碼異味IMO。

老實說,泛型重載並不是非常有用,除非你輸入泛型參數。我無法計算出錯誤輸入了多少次屬性,或者比較了兩種不同類型的屬性,並且它選擇了AreEqual(object,object)過載。因此,在運行時間稍晚給我一個失敗,而不是編譯時間。