代码之家  ›  专栏  ›  技术社区  ›  Thorsten Lorenz

NUnit的Is.EqualTo对于从泛型类派生的类是否不能可靠地工作?

  •  8
  • Thorsten Lorenz  · 技术社区  · 16 年前

    今天我遇到了NUnit的以下问题。

    我开始做一些序列化测试,并使用NUnit的Is.EqualTo()函数测试是否相等。

    当一个本该失败的测试通过时,我开始怀疑出了什么问题。当我使用obj1.Equals(obj2)时,它失败了。

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

    第二个测试中的两个(实际冲突的断言)表明了问题:

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

    因此,我深入研究了NUnit的源代码,结果发现,在对某些特殊条件执行了一些if()之后,ObjectsReequal(object x,object y)方法(最终通过Assert.that(x,Is.EqualTo(y))调用)来到了以下代码行:

    return x.Equals(y);
    

    我发现这非常令人困惑,因为我现在必须思考,也就是说.EqualTo()只需要走更长的路,但基本上应该和x.Equals(y)一样

    这里是感兴趣的人的完整方法(在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);
        }
    

    那么这里发生了什么,如何修复呢?

    我希望能够信任我的测试,因此必须再次尝试。

    提前谢谢。

    与此同时,我进一步努力解决这个问题,发现了一个类似的问题 here 并发布了一个可能的 workaround

    1 回复  |  直到 9 年前
        1
  •  5
  •   Jeff Sternal    16 年前

    问题是第二个测试的第二个断言调用 Equals 接受 object 而不是 Client<T> ,因此此比较返回false:

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

    if (obj.GetType() != this.GetType())
    
    推荐文章