137 votes

Comment tester l'équivalence des cartes dans Golang ?

J'ai un scénario de test piloté par tableau comme celui-ci :

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

Je pourrais vérifier si les longueurs sont les mêmes et écrire une boucle qui vérifie si chaque paire clé-valeur est la même. Mais je dois alors réécrire cette vérification lorsque je veux l'utiliser pour un autre type de carte (disons map[string]string ).

Ce que j'ai fini par faire, c'est convertir les cartes en chaînes de caractères et comparer les chaînes :

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

Cela suppose que les représentations des chaînes de caractères des cartes équivalentes sont les mêmes, ce qui semble être vrai dans ce cas (si les clés sont les mêmes, alors elles sont hachées vers la même valeur, donc leurs ordres seront les mêmes). Existe-t-il une meilleure façon de procéder ? Quelle est la façon idiomatique de comparer deux cartes dans des tests pilotés par des tableaux ?

261voto

joshlf Points 9622

La bibliothèque Go vous a déjà couvert. Faites-le :

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.")
}

Si vous regardez le code source pour reflect.DeepEqual 's Map vous verrez qu'il vérifie d'abord si les deux maps sont nulles, puis qu'elles ont la même longueur avant de vérifier enfin si elles ont le même ensemble de paires (clé, valeur).

Parce que reflect.DeepEqual prend un type d'interface, il fonctionnera sur toute carte valide ( map[string]bool, map[struct{}]interface{} etc). Notez qu'il fonctionnera également sur des valeurs non-map, donc faites attention à ce que ce que vous lui passez soit réellement deux maps. Si vous lui passez deux entiers, il vous dira volontiers s'ils sont égaux.

26voto

VonC Points 414372

Quelle est la façon idiomatique de comparer deux cartes dans des tests pilotés par des tableaux ?

Vous avez le projet go-test/deep pour aider.

Mais : cela devrait être plus facile avec Go 1.12 (février 2019) nativement : Voir notes de mise à jour .

fmt.Sprint(map1) == fmt.Sprint(map2)

fmt

Les cartes sont désormais imprimées dans l'ordre des clés pour faciliter les tests. .

Les règles de commande sont les suivantes :

  • Le cas échéant, nul correspond à faible
  • ints, floats, et chaînes de caractères, classés par <
  • Les comparaisons NaN sont inférieures aux flotteurs non-NaN
  • bool compare false avant true
  • Le complexe compare le réel, puis l'imaginaire
  • Comparaison des pointeurs par adresse machine
  • Comparaison des valeurs des canaux par l'adresse de la machine
  • Les structures comparent chaque champ à tour de rôle
  • Les tableaux comparent chaque élément à tour de rôle
  • Les valeurs d'interface sont comparées d'abord par reflect.Type décrivant le type concret, puis par valeur concrète, comme décrit dans les règles précédentes.

Lors de l'impression de cartes, les valeurs de clés non réflexives comme NaN étaient auparavant affichées comme <nil> . À partir de cette version, les valeurs correctes sont imprimées.

Sources :

Le CL ajoute : ( CL signifie "liste de changements". )

Pour ce faire, nous ajoutons un paquet à la Racine, internal/fmtsort qui implémente un mécanisme général pour trier les clés de la carte indépendamment de leur type.

C'est un peu désordonné et probablement lent, mais l'impression formatée de cartes n'a jamais été rapide et est déjà toujours axée sur la réflexion.

Le nouveau paquet est interne car nous ne voulons vraiment pas que tout le monde l'utilise pour trier les choses. Il est lent, non général, et ne convient qu'au sous-ensemble de types qui peuvent être des clés de carte.

Utilisez également le paquet dans text/template qui disposait déjà d'une version plus faible de ce mécanisme.

Vous pouvez voir cela utilisé dans src/fmt/print.go#printValue(): case reflect.Map:

16voto

zzzz Points 23017

Voici ce que je ferais (code non testé) :

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
}

12voto

ericson.cepeda Points 126

Utilisez cmp ( https://github.com/google/go-cmp ) à la place :

if !cmp.Equal(src, expectedSearchSource) {
    t.Errorf("Wrong object received, got=%s", cmp.Diff(expectedSearchSource, src))
}

Failed test

Elle échoue toujours lorsque l'"ordre" de la carte dans le résultat attendu n'est pas celui que votre fonction renvoie. Cependant, cmp est toujours capable d'indiquer où se trouve l'incohérence.

Pour référence, j'ai trouvé ce tweet :

https://twitter.com/francesc/status/885630175668346880?lang=en

"L'utilisation de reflect.DeepEqual dans les tests est souvent une mauvaise idée. http://github.com/google/go-cmp " - Joe Tsai

6voto

C.. Points 10739

Avis de non-responsabilité : Sans rapport avec map[string]int mais lié au test de l'équivalence des cartes en Go, qui est le titre de la question

Si vous avez une carte d'un type de pointeur (comme map[*string]int ), alors vous faire no vous voulez utiliser reflect.DeepEqual parce qu'elle renverra faux.

Enfin, si la clé est un type qui contient un pointeur non exporté, comme time.Time, alors reflect.DeepEqual sur une telle carte peut également retourner false .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X