web-dev-qa-db-ja.com

構造を比較するときに、このアサートがフォーマット例外をスローするのはなぜですか?

2つの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); 
}

これは意図された動作ですか?私はここで何か間違ったことをしていますか?

94
Kyle

私はそれを持っている。はい、それはバグです。

問題は、ここでstring.Formatの2つのレベルがあることです。

firstフォーマットのレベルは次のようなものです。

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

次に、指定したパラメーターでstring.Formatを使用します。

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

(明らかに、提供されている文化があり、some一種の消毒...しかし十分ではありません。)

文字列に変換された後、期待値と実際の値自体が中括弧で囲まれていない限り、これは問題ないように見えます。これは、Sizeに対して行います。たとえば、最初のサイズは次のように変換されます。

{Width=0, Height=0}

したがって、第2レベルのフォーマットは次のようになります。

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のそれほど怖くないコンテキストです。

回避策として、StriplingWarriorが提案するようにstring.Formatを使用できます。これにより、実際の値/期待値を使用したフォーマットの結果に対して実行される第2レベルのフォーマットが回避されます。

100
Jon Skeet

バグを見つけたと思います。

これは機能します(アサート例外をスローします):

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

@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は構造体です。オブジェクトはどうですか? param listdoesは、stringメッセージの後のリストがparams object[]であることを指定します。技術的には、はい構造体オブジェクトです...しかし、オブジェクトの特殊な種類つまり、値型。これがバグの原因だと思います。 Sizeと同様の使用法と構造を持つ独自のオブジェクトを使用する場合、実際には次のことが機能しますdoes work;

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);
}
5
DiskJunky

最初のアサートは正しくないと思います。

代わりにこれを使用してください:

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