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

Kyl*_*yle 94 c# unit-testing mstest exception-handling 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); 
}
Run Code Online (Sandbox Code Playgroud)

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

Jon*_*eet 100

我懂了.是的,这是一个错误.

问题是这里有两个层次string.Format.

第一格式的级别是一样的东西:

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

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

string finalMessage = string.Format(template, parameters);
Run Code Online (Sandbox Code Playgroud)

(显然还有所提供的文化,一些类型的消毒的......但这还不够.)

这看起来很好 - 除非预期值和实际值本身在转换为字符串后最终会出现括号 - 他们会这样做Size.例如,您的第一个尺寸最终会转换为:

{Width=0, Height=0}
Run Code Online (Sandbox Code Playgroud)

所以第二级格式化是这样的:

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

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

实际上,我们可以通过欺骗格式来使用我们的参数来预测和实际部分来证明这一点:

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

结果是:

Assert.AreEqual failed. Expected:<foo>. Actual:<bar>. What a surprise!
Run Code Online (Sandbox Code Playgroud)

显然已经破碎了,正如我们没想到的那样,foo也没有真正的价值bar!

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

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


Str*_*ior 43

我觉得你发现了一个bug.

这工作(抛出一个断言异常):

var a = 1;
var b = 2;
Assert.AreEqual(a, b, "Not equal {0} {1}", a, b);
Run Code Online (Sandbox Code Playgroud)

这工作(输出消息):

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

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

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

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

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


Dis*_*nky 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);
Run Code Online (Sandbox Code Playgroud)

我在代码使用方面做得更加明确,我一直在做一些实验.以下也不起作用;

// 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);
Run Code Online (Sandbox Code Playgroud)

// 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);
Run Code Online (Sandbox Code Playgroud)

这让我思考.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);
}
Run Code Online (Sandbox Code Playgroud)