为什么Assert.AreEqual仅在一个方向上失败,我的自定义类型具有扩展转换?

时间:2014-01-14 04:00:54

标签: vb.net nunit type-conversion equality implicit-conversion

我正在创建一个类似于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)中的错误吗?或者我错过了我的代码中的错误?

3 个答案:

答案 0 :(得分:3)

如果TDate,那么

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

如果您不喜欢与DateTimesomeWrapper.Equals(someDateTime)行为不一致,我建议最佳解决方案可能是someWrapper.Equals((Object)someDateTime)==的{​​{1}}过载}和!=并使用(DateTime, WrapperType)属性标记它们。这会导致编译器发出任何努力,直接将您的包装器类型的实例与(WrapperType, DateTime)进行比较,而无需先转换类型,以便它们匹配。