为什么这个断言在比较结构时抛出格式异常?

时间:2013-02-19 18:56:33

标签: c# unit-testing exception-handling mstest string-formatting

我正在尝试断言两个System.Drawing.Size结构的相等性,并且我得到一个格式异常而不是预期的断言失败。

[TestMethod]
public void AssertStructs()
{
    var struct1 = new Size(0, 0);
    var struct2 = new Size(1, 1);

    //This throws a format exception, "System.FormatException: Input string was not in a correct format."
    Assert.AreEqual(struct1, struct2, "Failed. Expected {0}, actually it is {1}", struct1, struct2); 

    //This assert fails properly, "Failed. Expected {Width=0, Height=0}, actually it is {Width=1, Height=1}".
    Assert.AreEqual(struct1, struct2, "Failed. Expected " + struct1 + ", actually it is " + struct2); 
}

这是预期的行为吗?我在这里做错了吗?

4 个答案:

答案 0 :(得分:100)

我知道了。是的,这是一个错误。

问题在于string.Format有两个级别。

第一个级别的格式化如下:

string template  = string.Format("Expected: {0}; Actual: {1}; Message: {2}",
                                 expected, actual, message);

然后我们将string.Format与您提供的参数一起使用:

string finalMessage = string.Format(template, parameters);

(显然有提供的文化,以及一些的消毒......但还不够。)

看起来很好 - 除非预期值和实际值在转换为字符串之后最终都带有括号 - 它们为Size执行。例如,您的第一个尺寸最终会转换为:

{Width=0, Height=0}

所以第二级格式化就像:

string.Format("Expected: {Width=0, Height=0}; Actual: {Width=1, Height=1 }; " +
              "Message = Failed expected {0} actually is {1}", struct1, struct2);

......这就是失败的原因。哎哟。

事实上,我们可以通过欺骗格式化来将这些参数用于预期和实际部分,从而轻松证明这一点:

var x = "{0}";
var y = "{1}";
Assert.AreEqual<object>(x, y, "What a surprise!", "foo", "bar");

结果是:

Assert.AreEqual failed. Expected:<foo>. Actual:<bar>. What a surprise!

很明显,因为我们没有期待foo,也没有实际价值bar

基本上这就像一个SQL注入攻击,但在string.Format的相当不那么可怕的上下文中。

作为解决方法,您可以使用string.Format作为StriplingWarrior建议。这避免了对使用实际/期望值进行格式化的结果执行第二级格式化。

答案 1 :(得分:43)

我认为你发现了一个错误。

这有效(抛出一个断言异常):

var a = 1;
var b = 2;
Assert.AreEqual(a, b, "Not equal {0} {1}", a, b);

这有效(输出信息):

var a = new{c=1};
var b = new{c=2};
Console.WriteLine(string.Format("Not equal {0} {1}", a, b));

但这不起作用(抛出FormatException):

var a = new{c=1};
var b = new{c=2};
Assert.AreEqual(a, b, "Not equal {0} {1}", a, b);

我想不出这是预期行为的任何原因。我提交了一份错误报告。与此同时,这是一个解决方法:

var a = new{c=1};
var b = new{c=2};
Assert.AreEqual(a, b, string.Format("Not equal {0} {1}", a, b));

答案 2 :(得分:5)

我同意@StriplingWarrior,这确实看起来确实是至少2次重载的Assert.AreEqual()方法的错误。正如StiplingWarrior已经指出的那样,以下失败;

var a = new { c = 1 };
var b = new { c = 2 };
Assert.AreEqual(a, b, "Not equal {0} {1}", a, b);

我在代码使用方面更加明确地进行了一些实验。以下内容也不起作用;

// specify variable data type rather than "var"...no effect, still fails
Size a = new Size(0, 0);
Size b = new Size(1, 1);
Assert.AreEqual(a, b, "Not equal {0} {1}", a, b);

// specify variable data type and name the type on the generic overload of AreEqual()...no effect, still fails
Size a = new Size(0, 0);
Size b = new Size(1, 1);
Assert.AreEqual<Size>(a, b, "Not equal {0} {1}", a, b);

这让我思考。 System.Drawing.Size是一个结构。对象怎么样?参数列表 指定string消息后的列表为params object[]。从技术上讲,是结构对象...但对象的特殊,即值类型。我认为这就是bug的所在。如果我们使用具有与Size类似的用法和结构的我们自己的对象,则以下确实工作;

private class MyClass
{
    public MyClass(int width, int height)
        : base()
    { Width = width; Height = height; }

    public int Width { get; set; }
    public int Height { get; set; }
}

[TestMethod]
public void TestMethod1()
{
    var test1 = new MyClass(0, 0);
    var test2 = new MyClass(1, 1);
    Assert.AreEqual(test1, test2, "Show me A [{0}] and B [{1}]", test1, test2);
}

答案 3 :(得分:3)

我认为第一个断言不正确。

请改用:

Assert.AreEqual(struct1, 
                struct2, 
                string.Format("Failed expected {0} actually is {1}", struct1, struct2));