Warum löst diese Behauptung beim Vergleichen von Strukturen eine Formatausnahme aus?

94

Ich versuche, die Gleichheit zweier System.Drawing.SizeStrukturen zu bestätigen, und erhalte eine Formatausnahme anstelle des erwarteten Bestätigungsfehlers.

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

Ist das beabsichtigtes Verhalten? Mache ich hier etwas falsch?

Kyle
quelle
Haben Sie versucht Assert.AreEqual(struct1, struct2, string.Format("Failed expected {0} actually is {1}, struct1.ToString (), struct2.ToString ())) `?
DiskJunky
Das funktioniert gut; Ich bin jedoch gespannt, warum Assert.AreEqual () einen String nicht mit Strukturtypen formatieren kann.
Kyle
@Kyle Aus Neugier ist dies nicht mit der Silverlight-kompatiblen Version des Unit Testing-Frameworks möglich, oder? Ich kann es mit diesen DLLs reproduzieren (habe noch nicht die vollständige .NET Framework-Version ausprobiert). BEARBEITEN: Nevermind, auch mit den vollständigen getestet und immer noch fehlgeschlagen. :)
Chris Sinclair
@ChrisSinclair Nein, dies verwendet jede Version von mstest, die mit Visual Studio 2010 Ultimate geliefert wird. Das Testprojekt selbst zielt auf .NET Framework 4
Kyle
4
Ich bin mir nicht sicher, ob es dir egal ist, aber das funktioniert gut in NUnit. Ich habe mehr "Probleme" wie diese in MStest gesehen. NUnit scheint etwas reifer zu sein (zumindest für mich). +1 für die Post
Bas

Antworten:

100

Ich habe es. Und ja, es ist ein Fehler.

Das Problem ist, dass es hier zwei Ebenen gibt string.Format.

Die erste Formatierungsstufe ist wie folgt:

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

Dann verwenden wir string.Formatmit den von Ihnen angegebenen Parametern:

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

(Offensichtlich werden Kulturen bereitgestellt und eine Art Desinfektion ... aber nicht genug.)

Das sieht gut aus - es sei denn, die erwarteten und tatsächlichen Werte selbst stehen nach der Konvertierung in eine Zeichenfolge in geschweiften Klammern - wofür sie dies tun Size. Zum Beispiel wird Ihre erste Größe konvertiert in:

{Width=0, Height=0}

Die zweite Formatierungsstufe ist also ungefähr so:

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

... und genau das scheitert. Autsch.

In der Tat können wir dies wirklich leicht beweisen, indem wir die Formatierung täuschen, um unsere Parameter für die erwarteten und tatsächlichen Teile zu verwenden:

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

Das Ergebnis ist:

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

Deutlich kaputt, da wir fooweder den tatsächlichen Wert erwartet hatten bar!

Im Grunde ist dies wie ein SQL-Injection-Angriff, aber in dem weniger beängstigenden Kontext von string.Format.

Als Problemumgehung können Sie verwenden, string.Formatwie von StriplingWarrior vorgeschlagen. Dadurch wird vermieden, dass die zweite Formatierungsstufe für das Ergebnis der Formatierung mit den tatsächlichen / erwarteten Werten ausgeführt wird.

Jon Skeet
quelle
Danke für die ausführliche Antwort Jon! Am Ende habe ich StriplingWarriors verwendet.
Kyle
1
Kein %*nÄquivalent? :(
Tom Hawtin - Tackline
Hat jemand einen Fehlerbericht dazu eingereicht?
Kevin
@ Kevin: Ja - obwohl intern, bin ich mir nicht sicher, ob der Fortschritt öffentlich sichtbar sein wird, bis er behoben ist.
Jon Skeet
1
@ Kevin Ich habe auch eine in MS eingegeben, sobald ein Fehler bestätigt wurde. connect.microsoft.com/VisualStudio/feedback/details/779528/… wenn Sie es öffentlich verfolgen möchten.
Kyle
43

Ich denke, Sie haben einen Fehler gefunden.

Dies funktioniert (löst eine Assert-Ausnahme aus):

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

Und das funktioniert (gibt die Nachricht aus):

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

Aber das funktioniert nicht (wirft a FormatException):

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

Ich kann mir keinen Grund vorstellen, warum dies ein erwartetes Verhalten wäre. Ich würde einen Fehlerbericht einreichen. In der Zwischenzeit ist hier eine Problemumgehung:

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

Ich stimme @StriplingWarrior zu, dass dies tatsächlich ein Fehler mit der Assert.AreEqual () -Methode bei mindestens 2 Überladungen zu sein scheint. Wie StiplingWarrior bereits betont hat, schlägt Folgendes fehl:

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

Ich habe ein wenig experimentiert, um die Verwendung von Code etwas expliziter zu gestalten. Das Folgende funktioniert auch nicht;

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

Und

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

Das brachte mich zum Nachdenken. System.Drawing.Size ist eine Struktur. Was ist mit Objekten? Die Parameterliste gibt an , dass die Liste nach der stringNachricht lautet params object[]. Technisch gesehen , ja structs sind Objekte ... aber spezielle Arten von Objekten, dh Werttypen. Ich denke, hier liegt der Fehler. Wenn wir verwenden unser eigenes Objekt mit einer ähnlichen Verwendung und Struktur Size, die folgenden tatsächlich tut Arbeit;

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
quelle
1
Das Problem ist nicht, ob es ist classoder struct, sondern ob der ToStringWert geschweifte Klammern enthält, die wie a aussehen String.Format.
Jean Hominal
3

Ich denke, die erste Behauptung ist falsch.

Verwenden Sie stattdessen Folgendes:

Assert.AreEqual(struct1, 
                struct2, 
                string.Format("Failed expected {0} actually is {1}", struct1, struct2));
Polaris
quelle
Laut Dokumentation sollte ich AreEqual mit einer formatierten Zeichenfolge aufrufen können. msdn.microsoft.com/en-us/library/ms243436%28v=vs.100%29.aspx , insbesondere Parameter Typ: System.Object [] Ein Array von Parametern, die beim Formatieren von Nachrichten verwendet werden sollen.
Kyle