NUnit 的 Is.EqualTo 对于从泛型类派生的类不能可靠地工作吗?
今天我在使用 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() 之后,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() (当测试失败时,前者不会给我这么好的
。
)
Today I ran into the following problem with NUnit.
I have a class, that derives from a generic class.
I started to do some serialization tests and tested for equality using the Is.EqualTo() function of NUnit.
I started to suspect something is wrong, when a test that should have failed passed instead. When I used obj1.Equals(obj2) instead it failed as it should.
To investigate I created the following tests:
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);
}
}
}
The two (actually conflicting Asserts) in the second test show the problem:
Assert.That(client1.Equals(client2), Is.True);
Assert.That(client1, Is.Not.EqualTo(client2));
This test should fail one way or the other, but it doesn't!
So I dug a little bit into NUnit's source code, only to find, that after some if()s for some special conditions, the ObjectsAreEqual(object x, object y) method (which eventually gets called via Assert.That(x, Is.EqualTo(y)), comes to this line of code:
return x.Equals(y);
I find that very perplexing, since I must now think, that Is.EqualTo() just takes a longer route, but basically should be doing the same as x.Equals(y)
Here the full method for whoever is interested (inside the NUNit.Framework.Constraints namespace):
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);
}
So what is going on here and how can it be fixed?
I want to be able to trust my tests and thus necessarily NUnit again.
I also don't want to start using Equals() instead of Is.EqualTo() (the former doesn't give me such a nice output when the test fails).
Thanks in advance.
Update:
In the meantime I wrestled further with this problem and found a similar problem here and posted a possible workaround.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
问题是第二个测试的第二个断言调用了接受
object
而不是Client
的Equals
重载,所以这个比较返回 false:要解决此问题,您可以将比较操作更改为:
The problem is that the second assertion of the second test calls the
Equals
overload that accepts anobject
rather than aClient<T>
, so this comparison returns false:To fix this, you can change the comparison operation to this: