Разве NUnit Is.EqualTo не работает надежно для классов, производных от универсальных классов? - PullRequest
8 голосов
/ 26 октября 2009

Сегодня я столкнулся со следующей проблемой с NUnit.

У меня есть класс, производный от универсального класса. Я начал делать некоторые тесты сериализации и проверял на равенство, используя функцию Is.EqualTo () NUnit.

Я начал подозревать, что что-то не так, когда вместо этого прошло испытание, которое должно было пройти неудачно. Когда я использовал 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 () для некоторых особых условий, метод ObjectsAreEqual (object x, object y) (который в итоге вызывается через Assert.That ( x, Is.EqualTo (y)), приходит к этой строке кода:

return x.Equals(y);

Я нахожу это очень озадачивающим, поскольку теперь я должен думать, что Is.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);
    }

Так что здесь происходит и как это можно исправить?

Я хочу быть в состоянии доверять своим тестам и, следовательно, обязательно NUnit снова.

Я также не хочу начинать использовать Equals () вместо Is.EqualTo () (первое не дает мне такой хороший вывод, когда тест не пройден).

Заранее спасибо.

Обновление:

Тем временем я продолжал бороться с этой проблемой и нашел похожую проблему здесь и опубликовал возможный обходной путь .

1 Ответ

5 голосов
/ 26 октября 2009

Проблема в том, что второе утверждение второго теста вызывает перегрузку Equals, которая принимает object вместо Client<T>, поэтому это сравнение возвращает false:

// obj.GetType() returns Client.DerrivedClient

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

Чтобы исправить это, вы можете изменить операцию сравнения так:

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