2009-10-26 35 views
8

Hoy me encontré con el siguiente problema con NUnit.¿El Is.EqualTo de NUnit no funciona de manera confiable para las clases derivadas de clases genéricas?

Tengo una clase, que se deriva de una clase genérica. Empecé a hacer algunas pruebas de serialización y probé la igualdad usando la función Is.EqualTo() de NUnit.

Comencé a sospechar que algo anda mal, cuando en su lugar pasó una prueba que debería haber fallado. Cuando usé obj1.Equals (obj2) en cambio, falló como debería.

Para investigar creé las siguientes pruebas:

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

Los dos (en realidad en conflicto Afirma) en la segunda prueba de mostrar el problema:

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

Esta prueba debe fallar de una manera u otra ¡pero no es así!

Así que profundicé un poco en el código fuente de NUnit, solo para encontrar que después de algunos if() para algunas condiciones especiales, el método ObjectsAreEqual (objeto x, objeto y) (que finalmente se llama a través de Assert.That (x, Is.EqualTo (y)), viene a esta línea de código:

return x.Equals(y); 

me parece muy desconcertante, ya que ahora hay que pensar, que Is.EqualTo() sólo toma una ruta más larga, pero básicamente debería hacer lo mismo que x.Equals (y)

Aquí el método completo para quien esté interesado (dentro del espacio de nombres 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); 
    } 

¿Qué está pasando aquí y cómo se puede solucionar?

Quiero ser capaz de confiar en mis pruebas y, por lo tanto, necesariamente NUnit nuevamente.

Tampoco quiero comenzar a usar Equals() en lugar de Is.EqualTo() (el primero no me da un resultado tan bueno cuando la prueba falla).

Gracias de antemano.

Actualización:

Mientras tanto luché más con este problema y se encontró un problema similar here y publicado una posible workaround.

Respuesta

5

El problema es que la segunda afirmación de la segunda prueba llama a la sobrecarga de Equals que acepta un object en lugar de un Client<T>, por lo que esta comparación devuelve false:

// obj.GetType() returns Client.DerrivedClient 

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

Para solucionar este problema, puede cambiar la comparación operación a esto:

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

Gracias Jeff, esto parece estar en el camino correcto. Mi ejemplo simple fue arreglado de esa manera, pero para el caso real todavía estoy luchando. Esto me enseñará en el futuro a no solo dar por sentada la validez del código generado. –

+0

Es un placer - Me imagino que el caso real es un dolor serio; mi único consejo es tomar un descanso (¡incluso uno corto!) para que puedas mirarlo con ojos nuevos. ¡Buena suerte! –

Cuestiones relacionadas