2009-10-26 4 views
8

Aujourd'hui, j'ai rencontré le problème suivant avec NUnit.Est-ce que Is.EqualTo de NUnit ne fonctionne pas de manière fiable pour les classes dérivées de classes génériques?

J'ai une classe, qui dérive d'une classe générique. J'ai commencé à faire des tests de sérialisation et testé l'égalité en utilisant la fonction Is.EqualTo() de NUnit.

J'ai commencé à soupçonner que quelque chose ne va pas, quand un test qui aurait dû échouer passé à la place. Quand j'ai utilisé obj1.Equals (obj2) à la place, il a échoué comme il se doit.

Enquêter j'ai créé les tests suivants:

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

Les deux (conflit en fait Affirme) dans le second test montrent le problème:

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

Ce test devrait échouer d'une manière ou l'autre mais ce n'est pas le cas! J'ai donc creusé un petit peu dans le code source de NUnit, seulement pour trouver, après certains if() s pour certaines conditions spéciales, la méthode ObjectsAreEqual (objet x, objet y) (qui est finalement appelée via Assert.That (x, Is.EqualTo (y)), vient à cette ligne de code:

return x.Equals(y); 

Je trouve cela très perplexe, car je dois maintenant penser que Is.EqualTo() prend juste une route plus longue, mais fondamentalement devrait faire la même chose que x.Equals (y)

Ici la méthode complète pour quiconque est intéressé (à l'intérieur de l'espace de noms 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); 
    } 

Alors, que se passe-t-il ici et comment peut-il être réparé? Je veux être en mesure de faire confiance à mes tests et donc nécessairement NUnit à nouveau.

Je ne veux pas non plus commencer à utiliser Equals() au lieu de Is.EqualTo() (le premier ne me donne pas une sortie si gentille quand le test échoue).

Merci d'avance.

Mise à jour:

En attendant, je luttais encore avec ce problème et a trouvé un problème similaire here et a affiché un workaround possible.

Répondre

5

Le problème est que la seconde affirmation du second test appelle la surcharge Equals qui accepte un object plutôt qu'un Client<T>, cette comparaison renvoie false:

// obj.GetType() returns Client.DerrivedClient 

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

Pour résoudre ce problème, vous pouvez modifier la comparaison opération à ceci:

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

Merci Jeff, cela semble être sur la bonne voie. Mon exemple simple a été corrigé de cette façon, mais pour le cas réel, je suis toujours en difficulté. Cela m'apprendra à l'avenir à ne pas prendre pour acquis la validité du code généré. –

+0

Mon plaisir - j'imagine que le cas réel est une douleur grave; mon seul conseil est de faire une pause (même une courte!) afin que vous puissiez le regarder avec des yeux frais. Bonne chance! –