2009-06-01 7 views
35

Mam test jednostki, aby sprawdzić, czy metoda zwraca poprawną IEnumerable. Metoda buduje przeliczalne przy użyciu yield return. Klasa że jest przeliczalny na to poniżej:W jaki sposób Assert.AreEqual określa równość dwóch ogólnych IEnumerables?

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

Jest to istotne częścią metody:

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

Gdybym zapisać wynik tej metody w actual, dokonać innego enumerable expected, i porównaj je w ten sposób ...

Assert.AreEqual(expected, actual); 

... Asercja nie udaje się.

napisałem metodę rozszerzenia dla IEnumerable że jest podobny do Python's zip function (łączy dwie IEnumerables na zestaw par) i próbowałem to:

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

Udało się! Jaka jest różnica między tymi dwoma Assert.AreEqual s?

+1

@Jason Baker: proszę nie brać tego niepowołane sposób, czy uważasz, że to, że musisz zadać takie pytanie, może oznaczać, że robisz rzeczy zbyt skomplikowane? –

+1

Erm ... nie, niezupełnie. :-) Czy możesz wskazać mi, gdzie robię rzeczy skomplikowane? –

+0

Ponadto, nie jestem przekonany, że za pomocą "text.GetHashCode()% type.GetHashCode();" jako wartość zwracana dla GetHashCode() to dobry pomysł ... –

Odpowiedz

25

zamierza porównać dwa przedmioty pod ręką. IEnumerable s są typami samymi w sobie i zapewniają mechanizm iteracji nad pewną kolekcją ... ale tak naprawdę nie są tą kolekcją. Twoje oryginalne porównanie porównało dwa IEnumerable s, co jest poprawnym porównaniem ... ale nie było to, czego potrzebujesz. Musisz porównać , co dwa IEnumerable s miały na celu wyliczyć.

Oto jak porównać dwa 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); 
} 

Nie jestem pewien, czy powyższe jest mniej kodu niż metody .Zip, ale to jest tak proste, jak to się robi.

+0

Ma to sens. Czy nie ma sposobu na porównanie dwóch obiektów IEnumerables przez obiekt bez konieczności pisania tak dużo kodu, jak to było potrzebne? –

+0

Po prostu użyłbym pętli while. Zaktualizowałem swoją odpowiedź na przykładzie. – jrista

+0

Znalazłem inny sposób, który działa i jest prostszy. Przyjmuję to, ponieważ pokazałeś, dlaczego mój kod nie działa. :-) –

86

Znaleziono go:

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

Nice find! Nie wiedziałem nawet, że istnieje rozszerzenie SequenceEqual(). Ty! – jrista

+0

Dziękuję bardzo. Zapisałeś moje pętle ;-) –

+3

To nie powie Ci żadnych informacji o tym, który element był nierówny, gdy test się nie powiedzie; po prostu ci powie, że się nie udało. Mechanizm CollectionAssert zasugerowany przez jerryjvla zapewnia znacznie bogatsze informacje o błędach. – bacar

47

Czy za pomocą klasy CollectionAssert zamiast ... biorąc pod uwagę, że jest on przeznaczony do wykonywania kontroli równości na zbiorach?

Uzupełnienie:
jeśli „zbiory” porównywane są wyliczenia, a następnie po prostu owijając je „new List<T>(enumeration)” jest najprostszym sposobem, aby wykonać porównanie. Zbudowanie nowej listy powoduje oczywiście pewne obciążenie, ale w kontekście testu jednostkowego nie powinno to mieć większego znaczenia, mam nadzieję?

+0

Wszystkie metody w kolekcji CollectionAssert są ustawione tak, aby były porównywane z obiektami ICollections. Mam IEnumerables. Czy istnieje jakiś prosty sposób na zmianę ich na ICollections? –

+0

W takich przypadkach zwykle robię szybkie owijanie w 'nowej liście (wyliczenie)', aby móc przeprowadzić porównanie. To nie tak, że narzut może stanowić problem w kontekście testu jednostkowego. – jerryjvl

+6

Po prostu wykonaj .ToArray() na obu argumentach przy użyciu CollectionAssert. – MEMark

18

myślę najprostszy i najczystszy sposób dochodzić równości chcesz jest kombinacją Odpowiedź jerryjvl i wypowiedzieć się na swoim stanowisku przez MEMark - połączyć CollectionAssert.AreEqual z metod rozszerzenie:

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

To daje bogatszy błąd informacje niż sugerowana przez OP (SequenceEqual) odpowiedź (powie ci, który element został znaleziony, co było nieoczekiwane).Na przykład:

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. 

Będziesz naprawdę zadowolony zrobiłeś to w ten sposób, jeśli/kiedy test nie powiedzie się - czasami można nawet wiedzieć, co się dzieje, bez konieczności wyłamać debugger - i hej jesteś robienie TDD w prawo, więc najpierw napiszesz test na wypadek niepowodzenia, prawda? ;-)

Komunikaty o błędach dostać nawet bardziej pomocne, jeśli używasz AreEquivalent do testowania równoważności (kolejność nie ma znaczenia):

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). 
Powiązane problemy