【问题标题】:Does NUnit's Is.EqualTo not work reliably for classes derived from generic classes?NUnit 的 Is.EqualTo 对于从泛型类派生的类是否不能可靠地工作?
【发布时间】:2009-10-26 13:39:24
【问题描述】:

今天我在使用 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);
        }
    }
}

第二个测试中的两个(实际上是冲突的Asserts)显示了问题:

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

这个测试应该以某种方式失败,但它不会!

所以我对 NUnit 的源代码进行了一些研究,结果发现,在针对某些特殊条件的一些 if() 之后,ObjectsAreEqual(object x, object y) 方法(最终通过 Assert.That( x, Is.EqualTo(y)),来到这行代码:

return x.Equals(y);

我觉得这很令人困惑,因为我现在必须想,Is.EqualTo() 只是需要更长的路线,但基本上应该和 x.Equals(y) 做同样的事情

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

那么这里发生了什么以及如何解决?

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

我也不想开始使用 Equals() 而不是 Is.EqualTo() (当测试失败时,前者不会给我这么好的输出)。

提前致谢。

更新:

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

【问题讨论】:

    标签: c# unit-testing generics tdd nunit


    【解决方案1】:

    问题在于第二个测试的第二个断言调用了Equals 重载,它接受object 而不是Client&lt;T&gt;,所以这个比较返回false:

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

    要解决这个问题,您可以将比较操作更改为:

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

    【讨论】:

    • 谢谢杰夫,这似乎是在正确的轨道上。我的简单示例以这种方式得到了修复,但对于实际情况,我仍在苦苦挣扎。这将教会我将来不要把生成的代码的有效性视为理所当然。
    • 我的荣幸 - 我想真实的情况是严重的痛苦;我唯一的建议是休息一下(即使是短暂的!),这样你就可以用新的眼光看待它。祝你好运!
    猜你喜欢
    • 2015-01-26
    • 2014-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-18
    • 2011-10-09
    • 2010-10-22
    • 2012-09-08
    相关资源
    最近更新 更多