2013-02-19 11 views
94

Sto tentando di affermare l'uguaglianza di due strutture System.Drawing.Size e sto ricevendo un'eccezione di formato anziché l'errore di asserzione previsto.Perché questa affermazione genera un'eccezione di formato quando si confrontano le strutture?

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

È questo comportamento previsto? Sto facendo qualcosa di sbagliato qui?

+0

hai provato ad avere 'Assert.AreEqual (struct1, struct2, string.Format (" Failed expected {0} in realtà è {1} ', struct1.ToString(), struct2.ToString()))'? – DiskJunky

+0

Che funziona bene, tuttavia sono curioso di sapere perché Assert.AreEqual() non è in grado di formattare una stringa con tipi di struttura. – Kyle

+0

@Kyle Per curiosità, questo non è con la versione compatibile Silverlight del framework Unit Testing, è ? Posso riprodurlo con quelle DLL (non ho ancora provato la versione completa di .NET framework) EDIT: non importa, testato anche con quelle complete e ancora fallito :) –

risposta

100

Ce l'ho. E sì, è un bug.

Il problema è che qui ci sono due livelli di string.Format.

Il prima livello di formattazione è qualcosa di simile:

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

Poi usiamo string.Format con i parametri che hai fornito:

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

(Ovviamente c'è culture essere fornite, e alcuni tipo di disinfezione ... ma non abbastanza.)

Che sembra a posto - a meno che i valori attesi e reali stessi finiscano con parentesi graffe, dopo essere stati convertiti in una stringa - che fanno per Size. Ad esempio, il primo dimensioni finisce per essere convertito in:

{Width=0, Height=0} 

Così il secondo livello di formattazione è qualcosa di simile:

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

... e questo è quello che sta fallendo. Ahia.

In effetti, siamo in grado di dimostrare davvero facilmente ingannare la formattazione di utilizzare i nostri parametri per le parti previsti ed effettivi:

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

Il risultato è:

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

Chiaramente rotto, come noi non mi aspettavo foo né era il valore attuale bar!

Fondamentalmente si tratta di un attacco SQL injection, ma nel contesto meno inquietante di string.Format.

Per ovviare al problema, è possibile utilizzare string.Format come suggerisce StriplingWarrior. Ciò evita che il secondo livello di formattazione venga eseguito sul risultato della formattazione con i valori effettivi/previsti.

+0

Grazie per la risposta dettagliata Jon! Ho finito per usare il lavoro di StriplingWarriors. – Kyle

+1

No '% * n' equivalenti? :( –

+0

Qualcuno ha presentato una segnalazione di bug per questo? – Kevin

3

Penso che la prima affermazione non sia corretta.

Utilizzare questo invece:

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

Secondo la documentazione dovrei essere in grado di chiamare AreEqual con una stringa formattata. Http://msdn.microsoft.com/en-us/library/ms243436%28v=vs.100%29.aspx, in particolare i parametri Tipo: System.Object [] Un array di parametri da utilizzare durante la formattazione del messaggio. – Kyle

43

Penso di aver trovato un bug.

Questo funziona (genera un'eccezione assert):

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

E questo funziona (emette il messaggio):

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

Ma questo non funziona (lancia una FormatException):

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

Non riesco a pensare a nessuna ragione per cui questo comportamento dovrebbe essere previsto. Presenterei un bug report. Nel frattempo, ecco una soluzione:

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

Sono d'accordo con @StriplingWarrior che questo effettivamente sembra essere un bug con il metodo Assert.AreEqual() in almeno 2 sovraccarichi. Come già sottolineato da Stipling Warrior, ciò che segue non riesce;

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

Ho fatto un po 'di esperimenti su questo ulteriore su un po' più esplicito nell'uso del codice. Il seguente non funziona neanche;

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

E

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

Questo mi ha fatto pensare. System.Drawing.Size è una struttura. E gli oggetti? L'elenco dei parametri fa specifica che l'elenco dopo il messaggio string è params object[]. Tecnicamente, yes structs sono oggetti ... ma speciali tipi di oggetti, ovvero tipi di valori. Penso che questo sia il posto dove si trova il bug. Se utilizziamo il nostro oggetto con un utilizzo e una struttura simili a Size, il seguente numero corrisponde a;

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

Il problema non è se 'class' o' struct', ma se il valore 'ToString' contenga parentesi graffe che assomigliano a' String .Format'. –

Problemi correlati