Perché questo asserisce lancia un'eccezione di formato quando si confrontano le strutture?


94

Sto cercando di affermare l'uguaglianza di due System.Drawing.Sizestrutture e ricevo un'eccezione di formato invece del previsto errore di asserzione.

[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 il comportamento previsto? Sto facendo qualcosa di sbagliato qui?


hai provato ad avere Assert.AreEqual(struct1, struct2, string.Format("Failed expected {0} actually is {1}, struct1.ToString (), struct2.ToString ())) `?
DiskJunky

Funziona bene; tuttavia sono curioso di sapere perché Assert.AreEqual () non può formattare una stringa con i tipi di struttura.
Kyle

@Kyle Per curiosità, questo non è con la versione compatibile con Silverlight del framework di Unit Testing, vero? 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. :)
Chris Sinclair

@ChrisSinclair no, sta usando qualsiasi versione di mstest fornita con Visual Studio 2010 ultimate. Il progetto di test stesso ha come target .NET Framework 4
Kyle

4
Non sono sicuro se te ne frega, ma funziona bene in NUnit. Ho visto più "problemi" come questi in MStest. NUnit sembra un po 'più maturo (almeno per me). +1 per il post
bas

Risposte:


100

Ho capito. E sì, è un bug.

Il problema è che ci sono due livelli in string.Formatcorso qui.

Il primo livello di formattazione è qualcosa del tipo:

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

Quindi usiamo string.Formatcon i parametri che hai fornito:

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

(Ovviamente vengono fornite culture e una sorta di sanificazione ... ma non abbastanza.)

Sembra a posto, a meno che i valori attesi ed effettivi non finiscano con le parentesi graffe, dopo essere stati convertiti in una stringa, cosa che fanno Size. Ad esempio, la tua prima taglia finisce per essere convertita in:

{Width=0, Height=0}

Quindi il secondo livello di formattazione è qualcosa del tipo:

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

... ed è questo che sta fallendo. Ahia.

In effetti, possiamo dimostrarlo molto facilmente ingannando la formattazione per utilizzare i nostri parametri per le parti previste ed effettive:

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 non ci aspettavamo fooné il valore effettivo bar!

Fondamentalmente questo è come un attacco SQL injection, ma nel contesto un po 'meno spaventoso di string.Format.

Come soluzione alternativa, puoi usare string.Formatcome suggerisce StriplingWarrior. Ciò evita che il secondo livello di formattazione venga eseguito sul risultato della formattazione con i valori effettivi / previsti.


Grazie per la risposta dettagliata Jon! Ho finito per usare StriplingWarriors.
Kyle

1
Nessun %*nequivalente? :(
Tom Hawtin - tackline

Qualcuno ha inviato una segnalazione di bug per questo?
Kevin,

@ Kevin: Sì, anche se internamente, quindi non sono sicuro che i progressi saranno visibili pubblicamente fino a quando non saranno risolti.
Jon Skeet

1
@Kevin Ne ho messo uno anche in MS una volta che è stato confermato un bug. connect.microsoft.com/VisualStudio/feedback/details/779528/… se vuoi monitorarlo pubblicamente.
Kyle

43

Penso che tu abbia trovato un bug.

Funziona (genera un'eccezione di asserzione):

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

E funziona (restituisce 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 (genera a 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 sarebbe un comportamento previsto. Presenterei una segnalazione di bug. Nel frattempo, ecco una soluzione alternativa:

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 sul fatto che questo sembra effettivamente essere un bug con il metodo Assert.AreEqual () su almeno 2 overload. Come StiplingWarrior ha già sottolineato, quanto segue fallisce;

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 ulteriormente per essere un po' più esplicito nell'uso del codice. Anche quanto segue non funziona;

// 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? La lista param non specifica che l'elenco dopo che il stringmessaggio è params object[]. Tecnicamente, sì, le strutture sono oggetti ... ma tipi speciali di oggetti, cioè tipi di valore. Penso che sia qui che si trova il bug. Se usiamo il nostro oggetto con un uso e una struttura simili a Size, quanto segue funziona effettivamente ;

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 sia classo struct, ma se il ToStringvalore contiene parentesi graffe che assomigliano a String.Format.
Jean Hominal

3

Penso che la prima affermazione non sia corretta.

Usa questo invece:

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

Secondo la documentazione dovrei essere in grado di chiamare AreEqual con una stringa formattata. 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.