为什么在比较结构时此断言会引发格式异常?


94

我试图断言两个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); 
}

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


您是否尝试过使用Assert.AreEqual(struct1, struct2, string.Format("Failed expected {0} actually is {1}struct1.ToString(),struct2.ToString()))?
DiskJunky

那很好 但是我很好奇为什么Assert.AreEqual()无法格式化具有结构类型的字符串。
凯尔

@Kyle出于好奇,这与单元测试框架的Silverlight兼容版本不是吗?我可以使用那些DLL(尚未尝试过完整的.NET Framework版本)来重现它。编辑:没关系,也对完整的DLL进行了测试,但仍然失败。:)
克里斯·辛克莱

@ChrisSinclair不,这使用的是Visual Studio 2010 Ultimate随附的任何版本的mstest。测试项目本身的目标是.NET Framework 4
Kyle

4
不知道您是否该死,但这在NUnit中可以正常工作。我在MStest中看到了更多类似的“问题”。NUnit似乎更加成熟(至少对我而言)。+1职位
bas

Answers:


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的建议使用。这样避免了对具有实际/期望值的格式化结果执行第二级格式化。


感谢乔恩的详细回答!我最终还是使用StriplingWarriors解决了。
凯尔

1
没有%*n同等的吗?:(
Tom Hawtin-大头钉

有人为此提交过错误报告吗?
凯文

@Kevin:是的-尽管是内部的,所以我不确定在修复之前是否可以公开看到进度。
乔恩·斯基特

1
@Kevin一旦确认有错误,我也会将它放入MS。connect.microsoft.com/VisualStudio/feedback/details/779528/…如果要公开跟踪它。
凯尔

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));

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[]。从技术上讲,是结构对象......但特殊类型的对象,即价值类型。我认为这就是错误所在。如果我们使用与相似的用法和结构的我们自己的对象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);
}

1
问题不在于是否是classstruct,但是否ToString值包含看起来像一个大括号String.Format
Jean Hominal 2013年

3

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

使用此代替:

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

根据文档,我应该能够使用格式化的字符串来调用AreEqual。msdn.microsoft.com/zh-cn/library/ms243436%28v=vs.100%29.aspx(特别是参数)类型:System.Object []格式化消息时要使用的参数数组。
凯尔
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.