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);
}
これは意図された動作ですか?私はここで何か間違ったことをしていますか?
私はそれを持っている。はい、それはバグです。
問題は、ここで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レベルのフォーマットが回避されます。
バグを見つけたと思います。
これは機能します(アサート例外をスローします):
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));
@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);
}
最初のアサートは正しくないと思います。
代わりにこれを使用してください:
Assert.AreEqual(struct1,
struct2,
string.Format("Failed expected {0} actually is {1}", struct1, struct2));