我正在创建一个类似于Nullable(Of T)
的包装类型,我正在编写一些单元测试来测试相等性。与Nullable(Of T)
一样,我在MyWrapperType(Of T)
和T
之间进行了隐式转换(两个方向)。因此,我希望NUnit中的所有以下测试都能通过:
Dim x = New MyWrapperType(Of DateTime)(Date.MaxValue)
Assert.True(Date.MaxValue = x)
Assert.True(x = Date.MaxValue)
Assert.True(Date.MaxValue.Equals(x))
Assert.True(x.Equals(Date.MaxValue))
Assert.AreEqual(x, Date.MaxValue)
Assert.AreEqual(Date.MaxValue, x)
他们都做了,除了最后一个。它告诉我:
失败:预期:9999-12-31 23:59:59.999但是:< 12/31/9999 11:59:59 PM>
以下是我的类型中可能相关的一些函数。 注意:我的类型的Value
属性与Nullable(Of T)
类似:
Public Shared Widening Operator CType(value As T) As MyWrapperType(Of T)
Return New MyWrapperType(Of T)(value)
End Operator
Public Shared Widening Operator CType(value As MyWrapperType(Of T)) As T
Return value.Value
End Operator
Public Overrides Function Equals(other As Object) As Boolean
If Me.Value Is Nothing Then Return other Is Nothing
If other Is Nothing Then Return False
Return Me.Value.Equals(other)
End Function
Public Overrides Function GetHashCode() As Integer
If Me.Value Is Nothing Then Return 0
Return Me.Value.GetHashCode()
End Function
当这些方法的断点设置为失败的测试方法时,除了ToString
之外,它们都没有被击中,当它们格式化要显示的错误时会被调用。
为什么对Assert.AreEqual
的此调用仅在一个方向失败?这是nunit.framework.dll
(使用版本2.6.1.12217)中的错误吗?或者我错过了我的代码中的错误?
答案 0 :(得分:3)
如果T
是Date
,那么
Return Me.Value.Equals(other)
other
作为Object
传递给Date.Equals
方法,如下所示:
Public Overrides Function Equals(ByVal value As Object) As Boolean
If TypeOf value Is DateTime Then
Dim time As DateTime = CDate(value)
Return (Me.InternalTicks = time.InternalTicks)
End If
Return False
End Function
正如您所看到的,第一个条件将返回False
。
Dim isdate As Boolean = (TypeOf CObj(New MyWrapperType(Of Date)(Date.MaxValue)) Is Date)
为确保正确投射,您可以执行以下操作:
Public Overrides Function Equals(other As Object) As Boolean
If (TypeOf other Is MyWrapperType(Of T)) Then
Dim obj As MyWrapperType(Of T) = DirectCast(other, MyWrapperType(Of T))
'...Me.Value.Equals(obj.Value)
ElseIf (TypeOf other Is T) Then
Dim obj As T = DirectCast(other, T)
'...Me.Value.Equals(obj)
End If
Return False
End Function
修改强>
如果我们反汇编Assert.AreEqual
方法,它看起来像这样:
调用1 :断言
Public Shared Sub AreEqual(ByVal expected As Object, ByVal actual As Object)
Assert.AreEqual(expected, actual, String.Empty, Nothing)
End Sub
调用2 :断言
Public Shared Sub AreEqual(ByVal expected As Object, ByVal actual As Object, ByVal message As String, ByVal ParamArray parameters As Object())
Assert.AreEqual(Of Object)(expected, actual, message, parameters)
End Sub
调用3 :断言
Public Shared Sub AreEqual(Of T)(ByVal expected As T, ByVal actual As T, ByVal message As String, ByVal ParamArray parameters As Object())
If Not Object.Equals(expected, actual) Then
Dim str As String
If (((Not actual Is Nothing) AndAlso (Not expected Is Nothing)) AndAlso Not actual.GetType.Equals(expected.GetType)) Then
str = CStr(FrameworkMessages.AreEqualDifferentTypesFailMsg(IIf((message Is Nothing), String.Empty, Assert.ReplaceNulls(message)), Assert.ReplaceNulls(expected), expected.GetType.FullName, Assert.ReplaceNulls(actual), actual.GetType.FullName))
Else
str = CStr(FrameworkMessages.AreEqualFailMsg(IIf((message Is Nothing), String.Empty, Assert.ReplaceNulls(message)), Assert.ReplaceNulls(expected), Assert.ReplaceNulls(actual)))
End If
Assert.HandleFail("Assert.AreEqual", str, parameters)
End If
End Sub
致电4 :对象
Public Shared Function Equals(ByVal objA As Object, ByVal objB As Object) As Boolean
Return ((objA Is objB) OrElse (((Not objA Is Nothing) AndAlso (Not objB Is Nothing)) AndAlso objA.Equals(objB)))
End Function
Assert.AreEqual(x,Date.MaxValue) = True
这最终将在此:
New MyWrapperType(Of DateTime)(Date.MaxValue).Equals(Date.MaxValue)
最终会调用您的Equals
方法:
Public Overrides Function Equals(other As Object) As Boolean
If Me.Value Is Nothing Then Return other Is Nothing <- Pass, Value is Date.MaxValue, not null
If other Is Nothing Then Return False <- Pass, other is Date.MaxValue, not null
Return Me.Value.Equals(other) <- Pass, Value (Date.MaxValue) = other (Date.MaxValue)
End Function
Assert.AreEqual(Date.MaxValue,x) = False
这最终将在此:
Date.MaxValue.Equals(New MyWrapperType(Of DateTime)(Date.MaxValue))
最终会调用您的Date.Equals(obj As Object)
方法:
Public Overrides Function Equals(ByVal value As Object) As Boolean
If TypeOf value Is DateTime Then '< Fail, value is not a DateTime, it's a MyWrapperType(Of T)
Dim time As DateTime = CDate(value)
Return (Me.InternalTicks = time.InternalTicks)
End If
Return False
End Function
答案 1 :(得分:1)
根据Bjørn-RogerKringsjå的回答,这是我理解的最佳原因:
当我致电Assert.True(Date.MaxValue.Equals(x))
时,会调用Date.Equals(other As Date)
覆盖,以及我的类型上的扩展运算符。看来编译器正在使用我的隐式类型转换在这里选择最具体的Equals
覆盖(Date
的覆盖)。
当我调用Assert.AreEqual(Date.MaxValue, x)
时,NUnit方法调用Object.Equal(a, b)
,然后将其委托给Date.Equals(other As Object)
方法。如果other
不是Date
,则此方法返回false。因此断言失败。
如果Assert.AreEqual
有一个覆盖日期(或者甚至可能是泛型类型T
的两个参数?)的覆盖,它可能会很好,但因为匹配的唯一覆盖是对象,我的类型转换无法挽救这一天。
答案 2 :(得分:1)
有两种方法可以转换结构类型以满足DateTime.Equals()
的某些重载:编译器可以使用隐式转换运算符来生成DateTime
,或者它可以执行到{的装箱转换{1}}。在许多情况下,当可能存在多个重载时,编译器应该假设它们是等效的,只需选择一个而不会发出嘎嘎声。不幸的是,Object
的重载并不等同(恕我直言,方法应该有不同的名称)。前四个测试有效,因为它们选择了一个重载,导致参数转换为进行比较的事物的类型。
第五个断言应该失败,因为它正在使用另一个不执行类型强制的重载。它成功的原因是您的Equals
方法无法遵守Equals
合同。任何类型的任何对象都不能合法地将自己报告为与任何不会回报的对象相等。由于没有Equals
认为自己等同于您的类型的未转换实例,因此您的类型不得认为自己等于未转换的DateTime
。
如果您不喜欢与DateTime
和someWrapper.Equals(someDateTime)
行为不一致,我建议最佳解决方案可能是someWrapper.Equals((Object)someDateTime)
和==
的{{1}}过载}和!=
并使用(DateTime, WrapperType)
属性标记它们。这会导致编译器发出任何努力,直接将您的包装器类型的实例与(WrapperType, DateTime)
进行比较,而无需先转换类型,以便它们匹配。