Mengapa pernyataan ini memunculkan pengecualian format saat membandingkan struktur?

94

Saya mencoba untuk menegaskan persamaan dua System.Drawing.Sizestruktur, dan saya mendapatkan pengecualian format daripada kegagalan pernyataan yang diharapkan.

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

Apakah ini perilaku yang dimaksudkan? Apakah saya melakukan sesuatu yang salah di sini?

Kyle
sumber
Assert.AreEqual(struct1, struct2, string.Format("Failed expected {0} actually is {1}sudahkah Anda mencoba memiliki , struct1.ToString (), struct2.ToString ())) `?
DiskJunky
Itu bekerja dengan baik; namun saya penasaran mengapa Assert.AreEqual () tidak dapat memformat string dengan tipe struktur.
Kyle
@Kyle Karena penasaran, ini bukan versi framework Pengujian Unit yang kompatibel dengan Silverlight, bukan? Saya dapat mereproduksinya dengan DLL tersebut (belum mencoba versi full .NET framework) EDIT: tidak apa-apa, diuji dengan yang lengkap juga dan masih gagal. :)
Chris Sinclair
@ChrisSinclair tidak, ini menggunakan versi mstest apa pun yang disertakan dengan Visual Studio 2010 ultimate. Proyek pengujian itu sendiri menargetkan .NET Framework 4
Kyle
4
Tidak yakin apakah Anda peduli, tetapi ini berfungsi dengan baik di NUnit. Saya telah melihat lebih banyak "masalah" seperti ini di MStest. NUnit tampaknya sedikit lebih dewasa (setidaknya bagi saya). 1 untuk pos
bas

Jawaban:

100

Aku memahaminya. Dan ya, itu bug.

Masalahnya adalah ada dua tingkat yang string.Formatterjadi di sini.

Itu pertama tingkat format adalah sesuatu seperti:

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

Kemudian kami menggunakan string.Formatdengan parameter yang Anda berikan:

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

(Jelas ada budaya yang disediakan, dan beberapa jenis sanitasi ... tapi tidak cukup.)

Kelihatannya baik-baik saja - kecuali nilai yang diharapkan dan nilai aktual itu sendiri diakhiri dengan tanda kurung di, setelah diubah menjadi string - yang mereka lakukan Size. Misalnya, ukuran pertama Anda akhirnya diubah menjadi:

{Width=0, Height=0}

Jadi level kedua dari pemformatan adalah seperti:

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

... dan itulah yang gagal. Aduh.

Memang, kami dapat membuktikan ini dengan sangat mudah dengan menipu pemformatan untuk menggunakan parameter kami untuk bagian yang diharapkan dan aktual:

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

Hasilnya adalah:

Assert.AreEqual failed. Expected:<foo>. Actual:<bar>. What a surprise!

Jelas rusak, seperti yang tidak kami duga foo dan bukan nilai sebenarnya bar!

Pada dasarnya ini seperti serangan injeksi SQL, tetapi dalam konteks yang agak kurang menakutkan string.Format .

Sebagai solusinya, Anda dapat menggunakan string.Formatseperti yang disarankan StriplingWarrior. Itu menghindari tingkat kedua pemformatan dilakukan pada hasil pemformatan dengan nilai aktual / yang diharapkan.

Jon Skeet
sumber
Terima kasih atas jawaban rinci Jon! Saya akhirnya menggunakan StriplingWarriors untuk mengatasi masalah ini.
Kyle
1
Tidak ada yang %*nsetara? :(
Tom Hawtin - tackline
Apakah ada yang mengirimkan laporan bug untuk ini?
Kevin
@Kevin: Ya - meskipun secara internal, jadi saya tidak yakin apakah kemajuan akan terlihat oleh publik sampai diperbaiki.
Jon Skeet
1
@Kevin Saya memasukkannya ke MS juga setelah dikonfirmasi sebagai bug. connect.microsoft.com/VisualStudio/feedback/details/779528/… jika Anda ingin melacaknya secara publik.
Kyle
43

Saya pikir Anda telah menemukan bug.

Ini berfungsi (melempar pengecualian assert):

var a = 1;
var b = 2;
Assert.AreEqual(a, b, "Not equal {0} {1}", a, b);

Dan ini berfungsi (mengeluarkan pesan):

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

Tapi ini tidak berhasil (melempar a FormatException):

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

Saya tidak bisa memikirkan alasan apa pun ini akan menjadi perilaku yang diharapkan. Saya akan mengirimkan laporan bug. Sementara itu, berikut solusinya:

var a = new{c=1};
var b = new{c=2};
Assert.AreEqual(a, b, string.Format("Not equal {0} {1}", a, b));
StriplingWarrior
sumber
5

Saya setuju dengan @StriplingWarrior bahwa ini memang tampaknya bug dengan metode Assert.AreEqual () pada setidaknya 2 kelebihan beban. Seperti yang telah ditunjukkan StiplingWarrior, berikut ini gagal;

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

Saya telah melakukan sedikit percobaan tentang hal ini lebih lanjut untuk menjadi sedikit lebih eksplisit dalam penggunaan kode. Berikut ini juga tidak berhasil;

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

Dan

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

Ini membuat saya berpikir. System.Drawing.Size adalah sebuah struct. Bagaimana dengan objek? Daftar param tidak menentukan bahwa daftar setelah stringpesan params object[]. Secara teknis, ya struct adalah objek ... tetapi jenis objek khusus, yaitu, tipe nilai. Saya pikir di sinilah letak bugnya. Jika kita menggunakan objek kita sendiri dengan penggunaan yang sama dan struktur Size, berikut ini benar-benar tidak bekerja;

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);
}
DiskJunky
sumber
1
Masalahnya bukan apakah itu classatau struct, tetapi apakah ToStringnilainya mengandung tanda kurung kurawal yang terlihat seperti a String.Format.
Jean Hominal
3

Saya pikir pernyataan pertama tidak benar.

Gunakan ini sebagai gantinya:

Assert.AreEqual(struct1, 
                struct2, 
                string.Format("Failed expected {0} actually is {1}", struct1, struct2));
Polaris
sumber
Menurut dokumentasi, saya seharusnya dapat memanggil AreEqual dengan string yang diformat. msdn.microsoft.com/en-us/library/ms243436%28v=vs.100%29.aspx , khususnya parameter Type: System.Object [] Sebuah array parameter untuk digunakan saat memformat pesan.
Kyle