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


94

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

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


Assert.AreEqual(struct1, struct2, string.Format("Failed expected {0} actually is {1}、struct1.ToString()、struct2.ToString())) ` を試してみましたか?
DiskJunky 2013

それはうまくいきます。ただし、なぜAssert.AreEqual()が構造体型の文字列をフォーマットできないのかについて知りたいです。
カイル

@Kyle好奇心から、これはユニットテストフレームワークのSilverlight互換バージョンではないですか?私はそれらのDLLでそれを再現できます(.NET Frameworkの完全なバージョンはまだ試していません)。:)
クリス・シンクレア

@ChrisSinclairいいえ、これはVisual Studio 2010 Ultimateに付属しているバージョンのmstestを使用しています。テストプロジェクト自体は.NET Framework 4を対象としています
カイル

4
あなたがいまいましいを与えるかどうかはわかりませんが、これはNUnitでうまく機能します。MStestでこれらのような「問題」をもっと見ました。NUnitはもう少し成熟しているようです(少なくとも私には)。投稿の+1
bas

回答:


100

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

問題は、2つのレベルがある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}

したがって、第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

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


詳細な回答をありがとうJon!最終的にはStriplingWarriorsを使用して回避しました。
カイル

1
%*n同等のものはありませんか?:(
Tom Hawtin-タックライン

誰かがこれについてバグレポートを提出しましたか?
ケビン

@Kevin:うん-内部的には、修正されるまで進行状況が公開されるかどうかはわかりません。
Jon Skeet

1
@Kevinバグが確認されたら、MSにも1つ入れました。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は構造体です。オブジェクトはどうですか?param list 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/en-us/library/ms243436%28v=vs.100%29.aspx、具体的にはパラメータタイプ:System.Object []メッセージのフォーマット時に使用するパラメータの配列。
カイル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.