繁体   English   中英

Microsoft.VisualStudio.TestTools.UnitTesting.Assert泛型方法重载行为

[英]Microsoft.VisualStudio.TestTools.UnitTesting.Assert generic method overloads behavior

Microsoft.VisualStudio.TestTools.UnitTesting命名空间中,有一个方便的静态类Assert来处理测试中发生的断言。

让我烦恼的是,大多数方法都非常重载,而且最重要的是,它们具有通用版本。 一个具体的例子是Assert.AreEqual ,它有18个重载,其中:

Assert.AreEqual<T>(T t1, T t2)

这种通用方法有什么用? 最初,我认为这是一种直接调用IEquatable<T> Equals(T t)方法的方法,但事实并非如此; 它将始终调用非泛型版本object.Equals(object other) 在编写了很多期望这种行为的单元测试之后,我发现了很难的方法(而不是像我应该的那样预先检查Assert类的定义)。

为了调用Equals的通用版本,泛型方法必须定义为:

Assert.AreEqual<T>(T t1, T t2) where T: IEquatable<T>

有没有一个很好的理由为什么不这样做?

是的,你没有为那些没有实现IEquatable<T>那些类型松开泛型方法,但它不是一个很大的损失,因为通过object.Equals(object other)检查相等,所以Assert.AreEqual(object o1, object o2)已经足够好了。

当前的通用方法是否提供了我没有考虑的优势,或者只是没有人停下来思考它的情况,因为它没有那么多的交易? 我看到的唯一优势是参数类型安全,但这似乎有点差。

编辑 :修复了一个错误,当我指的是IEquatable时我继续引用IComparable

具有该约束的方法将是非理想的,因为约束的常见问题不是签名的一部分

问题是对于任何未被其特定重载覆盖的T,编译器会选择通用的AreEqual<T>方法作为重载AreEqual<T>期间的最佳拟合,因为它确实是完全匹配。 在该过程的不同步骤中,编译器将评估T通过约束。 对于任何未实现IEquatable<T> ,此检查将失败,代码将无法编译。

请考虑单元测试库代码的简化示例以及库中可能存在的类:

public static class Assert
{
    public static void AreEqual(object expected, object actual) { }
    public static void AreEqual<T>(T expected, T actual) where T : IEquatable<T> { }
}

class Bar { } 

Class Bar不在约束中实现接口。 如果我们然后将以下代码添加到单元测试中

Assert.AreEqual(new Bar(), new Bar());

由于对最佳候选方法的约束不满意,代码将无法编译。 Bar代替T,这使它成为比object更好的候选者。)

类型'Bar'不能用作泛型类型或方法'Assert.AreEqual <T>(T,T)'中的类型参数'T'。 没有从'Bar'到'System.IEquatable <Bar>'的隐式引用转换。

为了满足编译器并允许我们的单元测试代码进行编译和运行,我们必须将至少一个输入转换为object的方法,以便可以选择非泛型重载,对于任何给定的情况都是如此T可能存在于您自己的代码或您希望在未实现接口的测试用例中使用的代码中。

Assert.AreEqual((object)new Bar(), new Bar());

所以必须提出这个问题 - 这是理想的吗? 如果您正在编写单元测试库,您是否会创建一个具有这种不友好限制的方法? 我怀疑你不会,微软单元测试库的实现者(无论是否出于这个原因)也没有。

基本上,泛型过载会强制您比较两个相同类型的对象。 如果您更改了期望值或实际值,则会出现编译错误。 这是MSDN博客很好地描述它。

你可以反编译方法,看看它真正做的就是添加一个类型检查(通过ILSpy),在我看来甚至没有正确完成(它在相等之后检查类型):

public static void AreEqual<T>(T expected, T actual, string message, params object[] parameters)
{
    message = Assert.CreateCompleteMessage(message, parameters);
    if (!object.Equals(expected, actual))
    {
        string message2;
        if (actual != null && expected != null && !actual.GetType().Equals(expected.GetType()))
        {
            message2 = FrameworkMessages.AreEqualDifferentTypesFailMsg((message == null) ? string.Empty : Assert.ReplaceNulls(message), Assert.ReplaceNulls(expected), expected.GetType().FullName, Assert.ReplaceNulls(actual), actual.GetType().FullName);
        }
        else
        {
            message2 = FrameworkMessages.AreEqualFailMsg((message == null) ? string.Empty : Assert.ReplaceNulls(message), Assert.ReplaceNulls(expected), Assert.ReplaceNulls(actual));
        }
        Assert.HandleFailure("Assert.AreEqual", message2);
    }
}

从理论上讲,它可以使用EqualityComparer<T>.Default来使用通用的Equals(如果可用),但是否则会回退到非泛型Equals。 这样就不需要对IEquatable<T>进行约束。 对于通用和非通用Equals方法具有不同的行为是代码气味,IMO。

老实说,除非输入泛型参数,否则泛型重载并不是非常有用。 我无法计算错误输入属性的次数或者比较了两个不同类型的属性并选择了AreEqual(object,object)重载。 因此,在运行时间而不是编译时间给我一个失败。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM