web-dev-qa-db-ja.com

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

長さが同じかどうかをチェックし、すべてのキーと値のペアが同じかどうかをチェックするループを作成できます。ただし、別のタイプのマップに使用する場合は、このチェックを再度作成する必要があります(たとえば、map[string]string)。

私がやったことは、マップを文字列に変換し、文字列を比較したことです:

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

これは、同等のマップの文字列表現が同じであると仮定していますが、この場合は正しいようです(キーが同じ場合、同じ値にハッシュされるため、順序は同じになります)。これを行うためのより良い方法はありますか?テーブル駆動テストで2つのマップを比較する慣用的な方法は何ですか?

65
andras

Goライブラリはすでにカバーしています。これを行う:

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

reflect.DeepEqualMapの場合に ソースコード を見ると、最初に両方のマップがnilであるかどうかをチェックし、次にそれらが同じかどうかをチェックすることがわかります。それらが同じ(キー、値)ペアのセットを持っているかどうかを最終的にチェックする前の長さ。

reflect.DeepEqualはインターフェース型をとるので、有効なマップ(map[string]bool, map[struct{}]interface{}など)で機能します。マップ以外の値でも機能することに注意してください。そのため、渡す値は実際には2つのマップであることに注意してください。 2つの整数を渡すと、それらが等しいかどうかがわかります。

127
joshlf

これは私がすることです(テストされていないコード):

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

テーブル駆動テストで2つのマップを比較する慣用的な方法は何ですか?

プロジェクト go-test/deep があります。

しかし:Go 1.12(2019年2月)nativelyリリースノート

fmt

テストを容易にするために、マップはキーでソートされた順に印刷されるようになりました

順序付け規則は次のとおりです。

  • 該当する場合、nilはlowと比較します
  • <によるint、float、stringsの順序
  • NaNは、NaN以外の浮動小数点数と比較します
  • boolfalsetrueの前に比較します
  • 複素数は実数と虚数を比較します
  • ポインタはマシンアドレスで比較します
  • チャネル値はマシンアドレスで比較します
  • 構造体は各フィールドを順番に比較します
  • 配列は各要素を順番に比較します
  • インターフェース値は、最初にreflect.Typeで具体的なタイプを記述し、次に前のルールで説明した具体的な値で比較します。

マップを印刷するとき、NaNなどの非再帰キー値は以前はとして表示されていました。このリリースでは、正しい値が出力されます。

ソース:

CLは以下を追加します:( CLは「変更リスト」を表します

これを行うには、 ルートにあるパッケージ、internal/fmtsort を追加します。これは、タイプに関係なくマップキーをソートするための一般的なメカニズムを実装します。

これは少し面倒で、おそらく遅いかもしれませんが、マップのフォーマットされた印刷は決して高速ではなく、すでにリフレクション駆動型です。

新しいパッケージは内部のものです。これを使用するすべての人が物事をソートすることを本当に望まないからです。一般的ではなく低速であり、マップキーにできる型のサブセットにのみ適しています。

また、text/templateのパッケージも使用します。このパッケージには、このメカニズムの脆弱なバージョンが既に含まれています。

src/fmt/print.go# で使用されていることがわかります

8
VonC

免責事項map[string]intとは関係ありませんが、Goのマップの等価性のテストに関係しています。これは質問のタイトルです

ポインタータイプのマップ(map[*string]intなど)がある場合は、 donotを使用してReflect.DeepEqualを使用します falseを返すため。

最後に、キーがtime.Timeのようなエクスポートされていないポインターを含むタイプである場合、そのようなマップでreflect.DeepEqual falseを返すこともできます

4
Carl

この回答 に触発され、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

github.com/google/go-cmp/cmp の「Diff」メソッドを使用します。

コード:

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

出力:

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