web-dev-qa-db-fra.com

Comment tester l'équivalence des cartes à Golang?

J'ai un cas de test basé sur une table 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 identique. Mais alors je dois réécrire ce chèque quand je veux l’utiliser pour un autre type de carte (disons map[string]string).

Ce que j'ai fini par faire est de convertir les cartes en chaînes et de 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 sous forme de chaîne des mappes équivalentes sont les mêmes, ce qui semble être vrai dans ce cas (si les clés sont les mêmes, elles sont hachées à la même valeur, leur ordre sera donc le même). Y a-t-il une meilleure manière de faire cela? Quelle est la manière idiomatique de comparer deux cartes dans des tests pilotés par des tables?

65
andras

La bibliothèque Go vous a déjà couvert. Faire ceci:

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 mappes sont nil, puis vérifie si elles ont la même longueur avant de vérifier si elles ont le même ensemble de (clé, valeur) paires.

Car reflect.DeepEqual prend un type d’interface, cela fonctionnera sur n’importe quelle carte valide (map[string]bool, map[struct{}]interface{}, etc). Notez que cela fonctionnera également sur les valeurs non-map. Veillez donc à ce que vous lui transmettiez deux cartes. Si vous passez deux entiers, il vous dira avec plaisir s'ils sont égaux.

127
joshlf

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

Quelle est la manière idiomatique de comparer deux cartes dans des tests pilotés par des tables?

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 version .

fmt

Les cartes sont maintenant imprimées dans un ordre de clé pour faciliter les tests .

Les règles de commande sont les suivantes:

  • Le cas échéant, rien ne se compare à faible
  • les commandes ints, floats et strings sont classées par <
  • NaN compare moins que les flotteurs non-NaN
  • bool compare false avant true
  • Complexe compare réel, puis imaginaire
  • Pointeurs comparés par adresse machine
  • Les valeurs de canal comparent par adresse machine
  • Les structures comparent chaque champ
  • Les tableaux comparent chaque élément
  • Les valeurs d'interface comparent 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é non réflexives telles que NaN étaient précédemment affichées sous la forme. A partir de cette version, les valeurs correctes sont imprimées.

Sources:

Le CL ajoute: ( CL signifie "Liste de changement" )

Pour cela, nous ajoutons n paquet à la racine, internal/fmtsort , qui implémente un mécanisme général pour trier les clés de carte, quel que soit leur type.

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

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

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

Vous pouvez voir que utilisé dans src/fmt/print.go#

8
VonC

Disclaimer : Sans lien avec map[string]int mais liée au test de l’équivalence des cartes dans Go, qui est le titre de la question

Si vous avez une carte de type pointeur (comme map[*string]int), alors vous voulez pas voulez utiliser reflect.DeepEqual car il renverra false.

Enfin, si la clé est un type contenant un pointeur non exporté, tel que time.Time, alors reflect.DeepEqual sur une telle carte peut également renvoyer false .

4
Carl

Inspiré par cette réponse , j’utilise les éléments suivants pour tester l’équivalence des cartes dans Golang:

import (
    "reflect"
)

func TestContinuationTokenHash(t *testing.T) {
    expected := []string{"35303a6235633862633138616131326331613030356565393061336664653966613733",
        "3130303a6235633862633138616131326331613030356565393061336664653966613733",
        "3135303a6235633862633138616131326331613030356565393061336664653966613733",
        "null"}
    actual := continuationTokenRecursion("null")
    if !reflect.DeepEqual(expected, actual) {
        t.Errorf("Maps not equal. Expected %s, but was %s.", expected, actual)
    }
}

https://github.com/030/nexus3-cli

0
030

Utilisez la méthode "Diff" de github.com/google/go-cmp/cmp :

Code:

// Let got be the hypothetical value obtained from some logic under test
// and want be the expected golden data.
got, want := MakeGatewayInfo()

if diff := cmp.Diff(want, got); diff != "" {
    t.Errorf("MakeGatewayInfo() mismatch (-want +got):\n%s", diff)
}

Sortie:

MakeGatewayInfo() mismatch (-want +got):
  cmp_test.Gateway{
    SSID:      "CoffeeShopWiFi",
-   IPAddress: s"192.168.0.2",
+   IPAddress: s"192.168.0.1",
    NetMask:   net.IPMask{0xff, 0xff, 0x00, 0x00},
    Clients: []cmp_test.Client{
        ... // 2 identical elements
        {Hostname: "macchiato", IPAddress: s"192.168.0.153", LastSeen: s"2009-11-10 23:39:43 +0000 UTC"},
        {Hostname: "espresso", IPAddress: s"192.168.0.121"},
        {
            Hostname:  "latte",
-           IPAddress: s"192.168.0.221",
+           IPAddress: s"192.168.0.219",
            LastSeen:  s"2009-11-10 23:00:23 +0000 UTC",
        },
+       {
+           Hostname:  "americano",
+           IPAddress: s"192.168.0.188",
+           LastSeen:  s"2009-11-10 23:03:05 +0000 UTC",
+       },
    },
  }
0
Jonas Felber