【问题标题】:Why does Assert.AreEqual(x, y) fail, but Assert.AreEqual(y, x) doesn't?为什么 Assert.AreEqual(x, y) 失败,但 Assert.AreEqual(y, x) 没有?
【发布时间】:2014-09-02 12:56:01
【问题描述】:

考虑这个结构:

public struct MyNumber
{
    private readonly int _value;

    public MyNumber(int myNumber)
    {
        _value = myNumber;
    }

    public int Value
    {
        get { return _value; }
    }

    public override bool Equals(object obj)
    {
        if (obj is MyNumber)
            return this == (MyNumber) obj;

        if (obj is int)
            return Value == (int)obj;

        return false;
    }

    public override string ToString()
    {
        return Value.ToString();
    }

    public static implicit operator int(MyNumber myNumber)
    {
        return myNumber.Value;
    }

    public static implicit operator MyNumber(int myNumber)
    {
        return new MyNumber(myNumber);
    }
}

当我在单元测试中这样做时:

Assert.AreEqual(new MyNumber(123), 123);

它是绿色的。

但是这个测试失败了:

Assert.AreEqual(123, new MyNumber(123));

为什么会这样?我猜这是因为 int 类决定了相等性,而在第一种情况下,我的类决定了它。但是我的课程可以隐式转换为int,所以这不是有帮助吗?

如何使 Assert.AreEqual 双向工作?顺便说一句,我正在使用 MSTest。

更新

实现IEquatable<int>IComparable<int> 没有帮助。

【问题讨论】:

  • 想知道是否值得实施IEquatable
  • @Charleh:如果您要将值存储在集合中,那么值得实现IEquatable<T>。它将加快ContainsIndexOf 等操作的速度。
  • 不是IEquatable@Charleh,IComparable<int>。这将确保该类也适用于 GreaterLess 断言。
  • 您使用的是哪个单元测试框架?您是使用Microsoft.VisualStudio.TestTools.UnitTesting.Assert 断言还是使用不同的单元测试框架?
  • 好问题,我确实在使用 MSTest。

标签: c# struct assert equality


【解决方案1】:

第一个断言将调用MyNumber.Equals,并且您已经实现了如果要比较的参数是Int32 并且它等于MyNumber 的值,它将成功。

第二个断言将调用Int32.Equals,它会失败,因为要比较的参数是MyNumberInt32 不知道或不理解。

您无法使第二个单元测试成功,因为您断言 Int32 应该等于您的值。不可能,因为它不同。 Int32.Equals 中的代码决定第二个值是否相等。您已经实现了一些可以进行单元测试的转换运算符,因此这些断言应该可以工作:

Assert.AreEqual(123, (int) new MyNumber(123));
Assert.AreEqual((MyNumber) 123, new MyNumber(123));

即使转换是在implicit 实现的,它们也不会被Assert.AreEquals 自动调用,因为此方法需要两个Object 类型的参数,并且您必须像我上面所做的那样显式调用它们。

由于您的 MyNumber.Equals 中的特殊处理,您现在有一个关于等号不可交换的类型,例如MyNumber(123) equals Int32(123) 是真的,但 Int32(123) equals MyNumber(123) 是假的。您应该避免这种情况,因此我建议您删除 MyNumber.Equals 中对 int 的特殊情况处理,而是依赖于大多数时候对您有用的隐式强制转换。如果他们不这样做,您将不得不进行明确的演员表。

【讨论】:

  • 但如果 MyNumber 是类,我认为实现接口 IEquatable,这两个断言都可以。不是吗?
  • @Davecz:我相信Assert.AreEqual 基本上映射到静态Object.Equals,在做一些简单的检查后会调用objA.Equals(objB)。我不相信在这个过程中使用了通用接口IEquatable<T>
  • 实现IComparable 如下所述将解决 OP 的问题。
  • 这个答案不包括 OP 的问题“我怎样才能让 Assert.AreEqual 双向工作?”
  • @RobEpstein: IComparable 是关于小于、等于或大于,例如它用于排序。在比较这个问题似乎涉及的相等性时,不使用此接口。
【解决方案2】:

引用Object.Equals

对于 Equals(Object) 方法的所有实现,以下陈述必须为真。在列表中,x、y 和 z 表示非 null 的对象引用。

  • ...

  • x.Equals(y) 返回与 y.Equals(x) 相同的值。

您的Equals 实现打破了这一硬性要求。 ((object)x).Equals(123)必须返回与((object)123).Equals(x)相同的值,如果x不是null,不管它是什么类型。

那里有很多代码,正确地假设要求两个对象中的哪一个执行比较并不重要。设计您的代码,使该假设不会失效。

实际上,这意味着您必须设计您的类,使其比较等于任何整数类型,无论您可能更喜欢多少否则。

【讨论】:

    【解决方案3】:

    在您的MyNumber 结构中实现IComparable<int>,如下所示:

        public int CompareTo(int other)
        {
            return other.CompareTo(Value);
        }
    

    这将确保所有断言按预期工作,例如:

        Assert.AreEqual(new MyNumber(123), 123);
        Assert.AreEqual(123, new MyNumber(123));
        Assert.Greater(124, new MyNumber(123));
        Assert.Less(124, new MyNumber(125));
    

    【讨论】:

    • 该结构按照 Asserts 中的设计工作,比较两个 MyNumbers
    • 这不起作用。正如 Martin 在 cmets 中提到的那样,IComporable 用于排序和排序。它不是用 Assert.AreEqual 调用的。
    • 这可能适用于您的单元测试框架,显然 Assert.GreaterAssert.Less 的出现与问题中使用的 MSTest 单元测试框架不同,但如果您使用 MSTest,它会失败。
    • 我回答时的原始问题没有提到使用的测试框架,所以我提供了一个在 NUnit 中完美运行的答案。我将尝试找到适用于 MSTest 的解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-26
    相关资源
    最近更新 更多