2013-08-13 19 views
36

Mam tabeli napędzane przypadek testowy jak ten:Testowanie równoważność mapy (Golang)

func CountWords(s string) map[string]int 

func TestCountWords(t *testing.T) { 
    var tests = []struct { 
    input string 
    want map[string]int 
    }{ 
    {"foo", map[string]int{"foo":1}}, 
    {"foo bar foo", map[string]int{"foo":2,"bar":1}}, 
    } 
    for i, c := range tests { 
    got := CountWords(c.input) 
    // TODO test whether c.want == got 
    } 
} 

mogę sprawdzić, czy długości są takie same i napisać pętlę, która sprawdza, czy każda para klucz-wartość Jest taki sam. Ale muszę ponownie napisać ten test, gdy chcę go użyć do innego rodzaju mapy (na przykład map[string]string).

Co skończyło się robi w tym, że przekształcone mapy do strun i porównywane ciągi:

func checkAsStrings(a,b interface{}) bool { 
    return fmt.Sprintf("%v", a) != fmt.Sprintf("%v", b) 
} 

//... 
if checkAsStrings(got, c.want) { 
    t.Errorf("Case #%v: Wanted: %v, got: %v", i, c.want, got) 
} 

ta zakłada, że ​​reprezentacje strunowe równoważnych mapy są takie same, co wydaje się być prawdą w tym przypadku (jeśli klawisze są takie same, to mają skrót do tej samej wartości, więc ich zamówienia będą takie same). Czy jest lepszy sposób to zrobić? Jaki jest idiomatyczny sposób porównywania dwóch map w testach sterowanych tabelą?

+3

Err, no: Kolejność Iteracja mapa nie jest gwarancją [przewidywalne] (http://golang.org/ref/spec#For_statements): _ "Kolejność iteracji na mapach nie jest określona i nie ma gwarancji, że będzie taka sama od jednej iteracji do następnej." "_. – zzzz

+2

Ponadto w przypadku map o określonych rozmiarach Go celowo losuje zamówienie. Jest wysoce wskazane, aby nie polegać na tym zamówieniu. –

Odpowiedz

77

Biblioteka Go już got you pokryte. Wykonaj:

import "reflect" 
// m1 and m2 are the maps we want to compare 
eq := reflect.DeepEqual(m1, m2) 
if eq { 
    fmt.Println("They're equal.") 
} else { 
    fmt.Println("They're unequal.") 
} 

Jeśli spojrzeć na source code dla reflect.DeepEqual „s Map przypadku, zobaczysz, że najpierw sprawdza, czy obie mapy są zerowe, a następnie sprawdza, czy mają taką samą długość, zanim ostatecznie do sprawdzenia zobacz, czy mają ten sam zestaw par (klucz, wartość).

Ponieważ reflect.DeepEqual przyjmuje typ interfejsu, będzie działał na dowolnej poprawnej mapie (map[string]bool, map[struct{}]interface{} itp.). Zauważ, że będzie działać również na wartościach innych niż mapy, więc uważaj, że to, co przekazujesz, to naprawdę dwie mapy. Jeśli przekażesz mu dwie liczby całkowite, z przyjemnością wskażesz, czy są one równe.

+0

Awesome, dokładnie tego szukałem. Domyślam się, że jnml twierdzi, że nie jest tak wydajny, ale kogo to obchodzi w przypadku testowym. – andras

+0

Tak, jeśli kiedykolwiek zechcesz to zrobić dla aplikacji produkcyjnej, zdecydowanie skorzystam z funkcji napisanej specjalnie, jeśli to możliwe, ale to zdecydowanie robi sztuczkę, jeśli wydajność nie jest problemem. – joshlf

+1

@andras Powinieneś również sprawdzić [gocheck] (http://labix.org/gocheck). Tak prosty jak 'c.Aser (m1, DeepEquals, m2)'. Co jest w tym miłe, to przerywa test i mówi, co masz i czego oczekiwałeś na wyjściu. – Luke

6

To jest to, co chciałbym zrobić (kod niesprawdzone):

func eq(a, b map[string]int) bool { 
     if len(a) != len(b) { 
       return false 
     } 

     for k, v := range a { 
       if w, ok := b[k]; !ok || v != w { 
         return false 
       } 
     } 

     return true 
} 
+0

OK, ale mam inny przypadek testowy, w którym chcę porównać wystąpienia 'map [string] float64'. 'eq' działa tylko dla' map [string] int' map. Czy powinienem zaimplementować wersję funkcji 'eq' za każdym razem, gdy chcę porównać instancje nowego typu mapy? – andras

+0

@andras: 11 SLOC. "Kopiuj wklej" specjalizuje się w krótszym czasie niż pytanie o to. Chociaż wiele innych osób użyłoby funkcji "odbicia", aby zrobić to samo, ale to znacznie gorsza wydajność. – zzzz

+1

Czy to nie oznacza, że ​​mapy będą w tej samej kolejności? Co nie gwarantuje wyświetlenia "Kolejności iteracji" na https: //blog.golang.org/go-maps-in-action – nathj07

1

Zastrzeżone: niezwiązane z map[string]int ale związane z badaniami równoważności mapach w Go, który jest tytuł pytanie

Jeśli masz mapę typu wskaźnika (jak map[*string]int), wtedy do not want to use reflect.DeepEqual ponieważ zwróci false.

Wreszcie, jeśli klucz jest typem, który zawiera nieodnotowany wskaźnik, np. Time.Time, a następnie reflect.DeepEqual na takiej mapie can also return false.

-2

Jedną z opcji jest, aby naprawić RNG:

rand.Reader = mathRand.New(mathRand.NewSource(0xDEADBEEF)) 
+0

dbać o wyjaśnienie sprawy? – Grozz