2009-10-26 3 views
8

Heute lief das folgende Problem mit NUnit.Funktioniert Is.EqualTo von NUnit nicht zuverlässig für Klassen, die von generischen Klassen abgeleitet sind?

Ich habe eine Klasse, die von einer generischen Klasse abgeleitet ist. Ich begann, einige Serialisierungstests zu machen und testete mit der Funktion Is.EqualTo() von NUnit auf Gleichheit.

Ich begann zu vermuten, dass etwas nicht stimmt, wenn ein Test, der nicht bestanden werden sollte, bestanden wurde. Wenn ich stattdessen obj1.Equals (obj2) verwendet habe, ist es fehlgeschlagen.

Um zu untersuchen, habe ich die folgenden Tests:

Assert.That(client1.Equals(client2), Is.True); 
Assert.That(client1, Is.Not.EqualTo(client2)); 

Dieser Test soll eine oder andere Weise fehlschlagen:

namespace NUnit.Tests 

{ 

using Framework; 

    public class ThatNUnit 
    { 
     [Test] 
     public void IsNotEqualTo_ClientsNotEqual_Passes() 
     { 
      var client1 = new DerrivedClient(); 
      var client2 = new DerrivedClient(); 

      client1.Name = "player1"; 
      client1.SomeGenericProperty = client1.Name; 
      client2.Name = "player2"; 
      client2.SomeGenericProperty = client2.Name; 

      Assert.That(client1.Equals(client2), Is.False); 
      Assert.That(client1, Is.Not.EqualTo(client2)); 
     } 

     [Test] 
     public void IsNotEqualTo_ClientsAreEqual_AlsoPasses_SomethingWrongHere() 
     { 
      var client1 = new DerrivedClient(); 
      var client2 = new DerrivedClient(); 

      client1.Name = "player1"; 
      client1.SomeGenericProperty = client1.Name; 
      client2.Name = client1.Name; 
      client2.SomeGenericProperty = client1.Name; 

      Assert.That(client1.Equals(client2), Is.True); 
      Assert.That(client1, Is.Not.EqualTo(client2)); 
     } 
    } 

    public class DerrivedClient : Client<string> 
    { 
    } 

    public class Client<T> 
    { 
     public string Name { get; set; } 

     public T SomeGenericProperty { get; set; } 

     public override bool Equals(object obj) 
     { 
      if (ReferenceEquals(null, obj)) 
      { 
       return false; 
      } 
      if (ReferenceEquals(this, obj)) 
      { 
       return true; 
      } 
      if (obj.GetType() != typeof(Client<T>)) 
      { 
       return false; 
      } 
      return Equals((Client<T>)obj); 
     } 

     public bool Equals(Client<T> other) 
     { 
      if (ReferenceEquals(null, other)) 
      { 
       return false; 
      } 
      if (ReferenceEquals(this, other)) 
      { 
       return true; 
      } 
      return Equals(other.Name, Name) && Equals(other.SomeGenericProperty, SomeGenericProperty); 
     } 

     public override int GetHashCode() 
     { 
      unchecked 
      { 
       return ((Name != null ? Name.GetHashCode() : 0) * 397)^SomeGenericProperty.GetHashCode(); 
      } 
     } 

     public override string ToString() 
     { 
      return string.Format("{0}, {1}", Name, SomeGenericProperty); 
     } 
    } 
} 

Die beide das Problem (eigentlich widersprüchliche Asserts) im zweiten Test zeigen aber das tut es nicht!

Also grub ich ein wenig in NUnit Quellcode, nur um zu finden, dass nach einigen if() s für einige spezielle Bedingungen, die ObjectsAreEqual (Objekt x, Objekt y) -Methode (die schließlich über Assert.That aufgerufen wird (x, Is.EqualTo (y)), kommt diese Codezeile:

return x.Equals(y); 

ich, dass es sehr verwirrend finden, da ich jetzt denken muss, dass Is.EqualTo() dauert nur eine längere Strecke, aber Grundsätzlich sollte das gleiche wie x.Equals (y)

Hier die vollständige Methode für wer interessiert ist (innerhalb der NUNit.Framework.Constraints Namespace):

public bool ObjectsEqual(object x, object y) 
    { 
     this.failurePoints = new ArrayList(); 

     if (x == null && y == null) 
      return true; 

     if (x == null || y == null) 
      return false; 

     Type xType = x.GetType(); 
     Type yType = y.GetType(); 

     if (xType.IsArray && yType.IsArray && !compareAsCollection) 
      return ArraysEqual((Array)x, (Array)y); 

     if (x is ICollection && y is ICollection) 
      return CollectionsEqual((ICollection)x, (ICollection)y); 

     if (x is IEnumerable && y is IEnumerable && !(x is string && y is string)) 
      return EnumerablesEqual((IEnumerable)x, (IEnumerable)y); 

     if (externalComparer != null) 
      return externalComparer.ObjectsEqual(x, y); 

     if (x is string && y is string) 
      return StringsEqual((string)x, (string)y); 

     if (x is Stream && y is Stream) 
      return StreamsEqual((Stream)x, (Stream)y); 

     if (x is DirectoryInfo && y is DirectoryInfo) 
      return DirectoriesEqual((DirectoryInfo)x, (DirectoryInfo)y); 

     if (Numerics.IsNumericType(x) && Numerics.IsNumericType(y)) 
      return Numerics.AreEqual(x, y, ref tolerance); 

     if (tolerance != null && tolerance.Value is TimeSpan) 
     { 
      TimeSpan amount = (TimeSpan)tolerance.Value; 

      if (x is DateTime && y is DateTime) 
       return ((DateTime)x - (DateTime)y).Duration() <= amount; 

      if (x is TimeSpan && y is TimeSpan) 
       return ((TimeSpan)x - (TimeSpan)y).Duration() <= amount; 
     } 

     return x.Equals(y); 
    } 

Also was ist hier los und wie kann es behoben werden?

Ich möchte meinen Tests und damit unbedingt NUnit wieder vertrauen können.

Ich möchte auch nicht mit Equals() anstelle von Is.EqualTo() beginnen (das ehemalige gibt mir keine so gute Ausgabe, wenn der Test fehlschlägt).

Vielen Dank im Voraus.

Update:

In der Zwischenzeit habe ich weiter mit diesem Problem gerungen und fand ein ähnliches Problem here und erzielen eine mögliche workaround.

Antwort

5

Das Problem ist, dass die zweite Behauptung des zweiten Tests ruft die Equals Überlastung, die ein object eher als ein Client<T> akzeptiert, so dass dieser Vergleich gibt false zurück:

// obj.GetType() returns Client.DerrivedClient 

if (obj.GetType() != typeof(Client<T>)) 
{ 
    return false; 
} 

Um dies zu beheben, können Sie den Vergleich ändern Bedienung dazu:

if (obj.GetType() != this.GetType()) 
+0

Danke Jeff, das scheint auf dem richtigen Weg zu sein. Mein einfaches Beispiel wurde auf diese Weise behoben, aber für den wahren Fall kämpfe ich immer noch. Das wird mich in der Zukunft lehren, nicht nur die Gültigkeit des generierten Codes für selbstverständlich zu halten. –

+0

Mein Vergnügen - ich stelle mir vor, der wirkliche Fall ist ein ernster Schmerz; mein einziger Rat ist, eine Pause zu machen (sogar eine kurze!), damit du sie mit frischen Augen sehen kannst. Viel Glück! –

Verwandte Themen