今天我在使用 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) 做的一样
这里有感兴趣的人的完整方法(在 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()(前者在测试失败时不会给我这么好的输出)。
提前致谢。
更新:
与此同时,我进一步解决了这个问题,发现了一个类似的问题here并发布了可能的 workaround .
最佳答案
问题是第二个测试的第二个断言调用了Equals
接受 object
的重载而不是 Client<T>
, 所以这个比较返回 false:
// obj.GetType() returns Client.DerrivedClient
if (obj.GetType() != typeof(Client<T>))
{
return false;
}
要解决此问题,您可以将比较操作更改为:
if (obj.GetType() != this.GetType())
关于c# - NUnit 的 Is.EqualTo 是否不能可靠地用于从泛型类派生的类?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1624848/