Golangでマップの同等性をテストする方法は?


92

私はこのようなテーブル駆動のテストケースを持っています:

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つのマップを比較する慣用的な方法は何ですか?


4
エラー、いいえ:マップを反復する順序は、予測可能であることが保証されていません:"マップの反復順序は指定されておらず、ある反復から次の反復まで同じであるとは保証されていません。..."
zzzz 2013

2
さらに、特定のサイズのマップの場合、Goは意図的に順序をランダム化します。その順序に依存しないことを強くお勧めします。
ジェレミーウォール

マップを比較しようとすることは、プログラムの設計上の欠陥です。
InancGumus19年

4
go 1.12(2019年2月)では、テストを容易にするためにマップがキーで並べ替えられた順序で印刷されるようになりました。参照してください以下の私の答え
VonC

回答:


174

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つの整数を渡すと、それらが等しいかどうかがわかります。


素晴らしい、それはまさに私が探していたものです。jnmlが言っていたように、パフォーマンスはそれほど高くありませんが、テストケースでは誰が気にしますか。
andras 2013

ええ、これが本番アプリケーションで必要な場合は、可能であればカスタム作成の関数を使用しますが、パフォーマンスが問題にならない場合は、これで間違いなくうまくいきます。
joshlf 2013

1
@andrasまた、チェックアウトする必要がありgocheckを。と同じくらい簡単c.Assert(m1, DeepEquals, m2)です。これの良いところは、テストを中止し、出力で何を取得し、何を期待したかを通知することです。
ルーク

8
DeepEqualでは、スライスのORDERも等しい必要があることに注意してください。
Xeoncross 2017


13

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

あなたにはgo-test/deep支援するプロジェクトがあります。

しかし:これはGo 1.12(2019年2月)でネイティブに簡単になるはずです:リリースノートを参照してください。

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

fmt

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

順序付けのルールは次のとおりです。

  • 該当する場合、nilは低いと比較します
  • int、float、stringsの順序 <
  • NaNは非NaNフロートよりも比較が少ない
  • boolfalse前に比較true
  • Complexは、実数、次に虚数を比較します
  • ポインタはマシンアドレスで比較します
  • チャネル値はマシンアドレスで比較されます
  • 構造体は各フィールドを順番に比較します
  • 配列は各要素を順番に比較します
  • インターフェイス値は、最初にreflect.Type具体的なタイプを記述して比較し、次に前のルールで説明した具体的な値を比較します。

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

出典:

CLは次を追加します:(CLは「変更リスト」の略です

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

これは少し面倒でおそらく遅いですが、マップのフォーマットされた印刷はかつてないほど速く、すでに常に反射駆動型です。

新しいパッケージは内部的なものです。これは、これを使用してすべての人が物事を分類することを本当に望んでいないためです。これは遅く、一般的ではなく、マップキーになり得るタイプのサブセットにのみ適しています。

またtext/template、このメカニズムのより弱いバージョンがすでにあるのパッケージを使用してください。

あなたはそれがで使われているのを見ることができます src/fmt/print.go#printValue(): case reflect.Map:


知らないので申し訳ありませんが、私はGoを初めて使用しますが、この新しいfmt動作はマップの同等性をテストするのにどのように役立ちますか?使用する代わりに文字列表現を比較することを提案していますDeepEqualか?
sschuberth

@sschuberthDeepEqualはまだ良いです。(またはむしろcmp.Equal)ユースケースは、元の問題で述べられているログの差分のように、twitter.com / mikesample / status / 1084223662167711744でより詳しく説明されています:github.com/golang/go/issues/21095。意味:テストの性質によっては、信頼できる差分が役立つ場合があります。
VonC

fmt.Sprint(map1) == fmt.Sprint(map2)TLため、DR
425nesp

@ 425nespありがとうございます。それに応じて回答を編集しました。
VonC

11

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

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
}

OKですが、のインスタンスを比較したい他のテストケースがあります map[string]float64。マップでeqのみ機能しmap[string]intます。eq新しいタイプのマップのインスタンスを比較するたびに、関数のバージョンを実装する必要がありますか?
andras 2013

@andras:11のSLOC。「コピーペースト」は、これについて尋ねるよりも短い時間でそれを専門にします。他の多くの人は同じことをするために「リフレクト」を使用しますが、それははるかに悪いパフォーマンスです。
zzzz 2013

1
マップが同じ順序になることを期待していませんか?どちらが保証されないかは、blog.golang.org / go
maps

3
@ nathj07いいえ、繰り返しaます。
Torsten Bronger 2016年

5

免責事項map[string]int質問のタイトルであるGoでのマップの同等性のテストとは無関係ですが、関連しています

ポインタ型(のようなmap[*string]int)のマップがある場合は、falseを返すため、reflect.DeepEqualを使用ないでください。

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


3

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",
+       },
    },
  }

2

代わりにcmp(https://github.com/google/go-cmp)を使用してください。

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

失敗したテスト

期待される出力のマップの「順序」が関数が返すものではない場合でも、失敗します。ただし、cmp不整合がどこにあるかを指摘することはできます。

参考までに、私はこのツイートを見つけました:

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

「テストでreflect.DeepEqualを使用することはしばしば悪い考えです。そのため、http://github.com/google/go-cmpをオープンソース化しています」-Joe Tsai


1

最も簡単な方法:

    assert.InDeltaMapValues(t, got, want, 0.0, "Word count wrong. Got %v, want %v", got, want)

例:

import (
    "github.com/stretchr/testify/assert"
    "testing"
)

func TestCountWords(t *testing.T) {
    got := CountWords("hola hola que tal")

    want := map[string]int{
        "hola": 2,
        "que": 1,
        "tal": 1,
    }

    assert.InDeltaMapValues(t, got, want, 0.0, "Word count wrong. Got %v, want %v", got, want)
}

-5

オプションの1つは、rngを修正することです。

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

すみませんが、あなたの答えはこの質問にどのように関連していますか?
Dima Kozhevin 2018年

@DimaKozhevin golangは、内部でrngを使用して、マップ内のエントリの順序を混合します。rngを修正すると、テスト目的で予測可能な順序が得られます。
Grozz 2018年

@Grozzそうですか?なぜ!?私は必ずしもそれがそうなるかもしれないと異議を唱えているわけではありません(私にはわかりません)私はそれがなぜそうなるのか分かりません。
msanford

私はGolangに取り組んでいないので、その理由を説明することはできませんが、それは少なくともv1.9の時点で確認された動作です。しかし、「マップの順序に依存するべきではないので、依存できないことを強制したい」という説明を見ました。
Grozz
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.