2009-06-01 9 views
35

Ho un test unitario per verificare se un metodo restituisce il valore corretto IEnumerable. Il metodo crea l'enumerabile usando yield return. La classe che si tratta di un enumerabile di è qui sotto:In che modo Assert.AreEqual determina l'uguaglianza tra due IEnumerables generici?

enum TokenType 
{ 
    NUMBER, 
    COMMAND, 
    ARITHMETIC, 
} 

internal class Token 
{ 
    public TokenType type { get; set; } 
    public string text { get; set; } 
    public static bool operator == (Token lh, Token rh) { return (lh.type == rh.type) && (lh.text == rh.text); } 
    public static bool operator != (Token lh, Token rh) { return !(lh == rh); } 
    public override int GetHashCode() 
    { 
     return text.GetHashCode() % type.GetHashCode(); 
    } 
    public override bool Equals(object obj) 
    { 
     return this == (Token)obj; 
    } 
} 

Questa è la parte rilevante del metodo:

foreach (var lookup in REGEX_MAPPING) 
{ 
    if (lookup.re.IsMatch(s)) 
    { 
     yield return new Token { type = lookup.type, text = s }; 
     break; 
    } 
} 

Se devo conservare il risultato di questo metodo in actual, fare un altro enumerabile expected, e confrontali in questo modo ...

Assert.AreEqual(expected, actual); 

..., l'asserzione fallisce.

Ho scritto un metodo di estensione per IEnumerable che è simile a Python's zip function (che combina due IEnumerables in una serie di coppie) e abbiamo provato questo:

foreach(Token[] t in expected.zip(actual)) 
{ 
    Assert.AreEqual(t[0], t[1]); 
} 

Ha funzionato! Quindi qual è la differenza tra questi due Assert.AreEqual s?

+1

@ Jason Baker: si prega di non prendere questo il male modo, hai considerato che il fatto di dover fare una domanda come questa potrebbe significare che stai rendendo le cose troppo complicate? –

+1

Ehm ... no, non proprio. :-) Potresti indicarmi dove sto complicando le cose? –

+0

Inoltre, non sono convinto che l'utilizzo di "text.GetHashCode()% type.GetHashCode();" come valore di ritorno per GetHashCode() è una buona idea ... –

risposta

25

Assert.AreEqual sta andando a confrontare i due oggetti a portata di mano. IEnumerable s sono tipi di per sé e forniscono un meccanismo per iterare su alcune raccolte ... ma in realtà non sono quelle raccolte. Il confronto originale ha confrontato due IEnumerable s, che è un confronto valido ... ma non quello che ti serviva. Era necessario confrontare in cui i due IEnumerable erano destinati a numerare.

Ecco come metto a confronto due enumerables:

Assert.AreEqual(t1.Count(), t2.Count()); 

IEnumerator<Token> e1 = t1.GetEnumerator(); 
IEnumerator<Token> e2 = t2.GetEnumerator(); 

while (e1.MoveNext() && e2.MoveNext()) 
{ 
    Assert.AreEqual(e1.Current, e2.Current); 
} 

io non sono sicuro se quanto sopra è meno codice di quanto il tuo metodo di .Zip, ma è quanto di più semplice come si arriva.

+0

Questo ha senso. Non c'è un modo per confrontare i due oggetti IEnumerables per oggetto senza dover scrivere tanto codice quanto è necessario? –

+0

Vorrei solo usare un ciclo while. Ho aggiornato la mia risposta con un esempio. – jrista

+0

Ho trovato un altro modo che funziona ed è più semplice. Lo accetterò dato che hai mostrato perché il mio codice non funzionava però. :-) –

86

trovato:

Assert.IsTrue(expected.SequenceEqual(actual)); 
+0

Bella scoperta! Non sapevo nemmeno che esistesse un'estensione SequenceEqual(). Ty! – jrista

+0

Grazie mille. Hai salvato i miei loop ;-) –

+3

Questo non ti dirà alcuna informazione su quale elemento non è stato uguale quando il test fallisce; ti dirà solo che è fallito. Il meccanismo CollectionAssert suggerito da jerryjvl fornisce informazioni di fallimento molto più complete. – bacar

47

Avete considerato utilizzando la classe CollectionAssert invece ... visto che esso è destinato a eseguire i controlli di parità su collezioni?

Addendum:
Se le 'collezioni' essere confrontati sono enumerazioni, allora semplicemente avvolgendoli con 'new List<T>(enumeration)' è il modo più semplice per eseguire il confronto. La costruzione di una nuova lista causa ovviamente un certo overhead, ma nel contesto di un unit test questo non dovrebbe importare troppo spero?

+0

Tuttavia, tutti i metodi in CollectionAssert vengono confrontati con ICollections. Ho IEnumerables. C'è un modo semplice per cambiarli in ICollections? –

+0

In casi del genere, di solito faccio un rapido riassunto in una 'nuova lista (enumerazione)' in modo che io possa eseguire il confronto. Non è che il sovraccarico possa costituire un problema nel contesto di un test unitario. – jerryjvl

+6

Basta fare .ToArray() su entrambi gli argomenti quando si usa CollectionAssert. – MEMark

18

Penso che il modo più semplice e più chiaro per affermare l'uguaglianza che si desidera è una combinazione della risposta da jerryjvl e commento sul suo post di MEMark - combinare CollectionAssert.AreEqual con metodi di estensione:

CollectionAssert.AreEqual(expected.ToArray(), actual.ToArray()); 

Questo dà l'errore più ricco informazioni rispetto alla risposta SequenceEqual suggerita dall'OP (ti dirà quale elemento è stato trovato che era inaspettato).Per esempio:

IEnumerable<string> expected = new List<string> { "a", "b" }; 
IEnumerable<string> actual = new List<string> { "a", "c" }; // mismatching second element 

CollectionAssert.AreEqual(expected.ToArray(), actual.ToArray()); 
// Helpful failure message! 
// CollectionAssert.AreEqual failed. (Element at index 1 do not match.)  

Assert.IsTrue(expected.SequenceEqual(actual)); 
// Mediocre failure message: 
// Assert.IsTrue failed. 

È ci sarà davvero piacque avete fatto in questo modo se/quando il test non viene superato - a volte si può anche sapere che cosa c'è che non va, senza dover rompere il debugger - e hey tu sei facendo correttamente TDD, quindi prima scrivi un test negativo, giusto? ;-)

I messaggi di errore si fanno ancora più utile se si sta utilizzando AreEquivalent per verificare l'equivalenza (ordine non importa):

CollectionAssert.AreEquivalent(expected.ToList(), actual.ToList()); 
// really helpful error message! 
// CollectionAssert.AreEquivalent failed. The expected collection contains 1 
// occurrence(s) of <b>. The actual collection contains 0 occurrence(s).