2014-09-03 7 views
6

Im Namespace Microsoft.VisualStudio.TestTools.UnitTesting gibt es die handliche statische Klasse Assert, um die Behauptungen in Ihren Tests zu behandeln.Microsoft.VisualStudio.TestTools.UnitTesting.Assert generische Methode Überladungen Verhalten

Etwas, das mich abgehört hat, ist, dass die meisten Methoden extrem überlastet sind, und darüber hinaus haben sie eine generische Version. Ein konkretes Beispiel ist Assert.AreEqual die 18 Überlastungen hat, darunter:

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

Was ist die Verwendung dieser generischen Methode? Ursprünglich dachte ich, dies sei eine Möglichkeit, die Methode IEquatable<T> Equals(T t) direkt aufzurufen, aber das ist nicht der Fall. Es wird immer die nicht-generische Version object.Equals(object other) aufgerufen. Ich habe den harten Weg herausgefunden, nachdem ich ein paar Unit-Tests programmiert habe, die dieses Verhalten erwarten (anstatt die Klassendefinition Assert im Voraus zu prüfen, wie ich es hätte tun sollen).

Um definiert werden würde, um die generische Version von Equals, die generische Methode musste zu nennen wie:

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

Gibt es einen guten Grund, warum es nicht so gemacht wurde?

Ja, verlieren Sie die generische Methode für alle jene Typen, die nicht implementieren IEquatable<T>, aber es ist kein großer Verlust sowieso als Gleichheit durch object.Equals(object other) geprüft werden würde, so Assert.AreEqual(object o1, object o2) ist schon gut genug.

Bietet die aktuelle generische Methode Vorteile, die ich nicht in Betracht ziehe, oder ist es nur so, dass niemand aufhörte, darüber nachzudenken, da es nicht so viel von einem Deal ist? Der einzige Vorteil, den ich sehe, ist die Sicherheit des Argumenttyps, aber das scheint irgendwie schlecht zu sein.

bearbeiten: Ein Fehler wurde behoben, wo ich IComparable Bezug gehalten, als ich IEquatable gemeint.

+0

Sie können nicht einschränken, da jede T ohne eigene Überlastung natürlich an diese Methode als Teil der Überladungsauflösung lösen würde und erst dann der Compiler über die unerfüllte Einschränkung für jede T beklagen haben, die nicht die Schnittstelle nicht implementiert. Was Ihre falsche Annahme betrifft, haben Sie die Dokumentation der Methode überprüft, bevor Sie Ihre Teststrategie darauf aufbauen? –

+0

AnthonyPegram: Wenn Sie es beschränken, können Sie kein T übergeben, das die Schnittstelle nicht implementiert, also wie genau würde die Überladungsauflösung in Laufzeit fehlschlagen? – InBetween

+1

Es würde zur Kompilierzeit fehlschlagen, aber die Überladungsauflösung hätte sich bereits für die T-Überladung entschieden und * dann * die Beschränkung geprüft (und fehlgeschlagen). Constraints sind nicht Teil der Signatur. Wenn Sie diesen Ausdruck suchen, finden Sie viele Ressourcen (hier und anderswo, vor allem Eric Lipperts Blog), die Ihnen mehr Details dazu geben werden. –

Antwort

4

Die Methode mit dieser Einschränkung wäre wegen des häufig auftretenden Problems constraints not being part of the signature nicht ideal.Das Problem wäre, dass für jeden T, der nicht durch seine eigene spezifische Überladung abgedeckt ist, der Compiler die generische Methode AreEqual<T> als die beste Lösung während der Überladungsauflösung wählen würde, da dies in der Tat durch eine exakte Übereinstimmung erfolgen würde. In einem anderen Schritt des Prozesses würde der Compiler auswerten, dass T die Beschränkung besteht. Für jedes T, die nicht IEquatable<T> nicht implementiert, würde diese Prüfung fehl, und der Code nicht kompilieren.

Betrachten Sie dieses vereinfachte Beispiel des Unit-Tests Bibliothek Code und eine Klasse, die in Ihrer Bibliothek existieren könnte:

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 { } 

Klasse Bar nicht implementiert die Schnittstelle in der Beschränkung. Wenn wir dann den folgenden Code zu einer Einheit Test

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

Der Code hinzuzufügen waren nicht wegen der unbefriedigten Einschränkung für die Methode kompilieren, die der beste Kandidat ist. (Bar Ersatz für T, die es zu einem besseren Kandidaten als object macht.)

Der Typ ‚Bar‘ kann nicht als Typ-Parameter ‚T‘ verwendet werden, in dem generischen Typ oder Methode ‚Assert.AreEqual <T> (T, T) '. Es gibt keine implizite Referenzkonvertierung von ‚Bar‘ auf ‚System.IEquatable <Bar>‘.

Um den Compiler und zulassen, dass unsere Einheit Testcode zu kompilieren und auszuführen zu erfüllen, würden wir werfen haben mindestens einen Eingang auf das Verfahren zu object so dass die nicht-generischen Überlast gewählt werden, und dies würde für jede gegeben T, die Sie in Ihrem eigenen Code oder Code existieren könnte verbrauchen, dass Sie in Ihren Testfällen verwenden möchten, die nicht die Schnittstelle nicht implementiert.

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

Also muss die Frage gestellt werden - wäre das ideal? Würden Sie eine solche Methode mit solch einer unfreundlichen Einschränkung erstellen, wenn Sie eine Unit-Testing-Bibliothek schreiben würden? Ich vermute, Sie würden es nicht tun, und die Implementierer der Microsoft-Unit-Test-Bibliothek (ob aus diesem Grund oder nicht) haben auch nicht.

+0

+1 Danke, dass Sie mich verstehen, was vor sich geht. Ich frage mich jedoch, warum Typbeschränkungen nicht Teil der Überladungsauflösung sind ... Außerdem frage ich mich, warum der Compiler einen Fehler auslöst, anstatt eine Warnung auszugeben und auf die AreEqual (Objekt erwartet ... 'one) zurückzufallen. –

1

Grundsätzlich zwingt die generische Überladung Sie, zwei Objekte desselben Typs zu vergleichen. Wenn Sie den Typ des erwarteten oder tatsächlichen Werts ändern, wird der Kompilierungsfehler angezeigt. Hier ist MSDN blog beschreibt es ziemlich gut.

+0

Ja, das ist, was ich in der Frage erwähne ... der einzige Vorteil ist die Sicherheit des Argumenttyps, aber deshalb ist dieser Grund in diesem speziellen Fall eher schlecht. – InBetween

1

Sie können die Methode dekompilieren und sehen, dass alle es wirklich tut, ist eine Typprüfung (via ILSpy) hinzufügen, die korrekt in meiner Meinung nach getan ist nicht einmal (er prüft Arten nach Gleichheit):

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); 
    } 
} 

Theoretisch könnte EqualityComparer<T>.Default verwendet werden, um die generischen Equals zu verwenden, falls verfügbar, die aber auf nicht-generische Equalizer zurückgreifen würden. Dies würde dann keine Beschränkung auf IEquatable<T> erfordern. Ein anderes Verhalten für die generischen und nicht-generischen Equals-Methoden ist ein Code-Geruch, IMO.

Ehrlich gesagt ist die generische Überladung nicht sehr nützlich, es sei denn, Sie geben den generischen Parameter ein. Ich kann nicht zählen, wie oft ich eine Eigenschaft vertippt oder zwei Eigenschaften verschiedener Typen verglichen habe und die AreEqual(object,object) Überladung ausgewählt habe. Dies führt zu einem Fehler viel später zur Laufzeit statt zur Kompilierzeit.