2009-10-26 12 views
8

Dzisiaj natknąłem się na następujący problem z NUnit.Czy NUnit's Is.EqualTo nie działa niezawodnie dla klas wywodzących się z klas ogólnych?

Mam klasę, która pochodzi z klasy ogólnej. Zacząłem wykonywać kilka testów na serializację i testowałem na równość przy użyciu funkcji Is.EqualTo() NUnit.

Zacząłem podejrzewać, że coś jest nie tak, gdy zamiast tego przeszedł test, który powinien zakończyć się niepowodzeniem. Kiedy użyłem obj1.Equals (obj2) zamiast tego, nie powiodło się tak jak powinno.

celu zbadania stworzyłem następujące testy:

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

Dwa (właściwie sprzeczne twierdzi) w drugim teście pokazać problem:

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

Ten test nie powiedzie się w taki czy inny , ale tak nie jest!

Więc zagłębiłem się trochę w kodzie źródłowym NUnit, tylko po to, aby po jakimś warunku if() dla pewnych specjalnych warunków użyć metody ObjectsAreEqual (object x, object y) (która ostatecznie zostanie wywołana przez Assert.That (x, Is.EqualTo (y)), dochodzi do tego wiersza kodu:

return x.Equals(y); 

Uważam, że bardzo kłopotliwy, ponieważ muszę teraz myśleć, że Is.EqualTo() po prostu ma dłuższą drogę, ale zasadniczo powinien być taki sam jak x.Equals (y)

Tutaj pełna metoda dla każdego, kto jest zainteresowany (w przestrzeni nazw NUNit.Framework.Constraints):

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

Co tu się dzieje i jak można to naprawić?

Chcę móc zaufać moim testom i tym samym koniecznie ponownie NUnit.

Również nie chcę zacząć używać Equals() zamiast Is.EqualTo() (ten pierwszy nie daje mi tak fajnego wyniku, gdy test się nie powiedzie).

Z góry dziękuję.

Aktualizacja:

W międzyczasie zmagałem się dalej z tym problemem i znalazłem podobny problem here i pisał ewentualnego workaround.

Odpowiedz

5

Problem polega na tym, że drugie twierdzenie drugiego badania wywołuje przeciążenie Equals która akceptuje object zamiast Client<T>, więc porównanie to zwraca fałsz:

// obj.GetType() returns Client.DerrivedClient 

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

Aby rozwiązać ten problem, można zmienić porównania operacja do tego:

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

Dzięki Jeff, wydaje się, że jest na dobrej drodze. Mój prosty przykład został naprawiony w ten sposób, ale w prawdziwym przypadku wciąż walczę. To nauczy mnie w przyszłości nie tylko przyjmować ważność wygenerowanego kodu za pewnik. –

+0

Moja przyjemność - wyobrażam sobie, że prawdziwy przypadek to poważny ból; moją jedyną radą jest zrobić sobie przerwę (nawet krótką!), abyś mógł spojrzeć na to świeżym okiem. Powodzenia! –

Powiązane problemy