Golang'daki haritaların denkliği nasıl test edilir?


92

Bunun gibi masaya dayalı bir test durumum var:

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

Uzunlukların aynı olup olmadığını kontrol edebilir ve her anahtar / değer çiftinin aynı olup olmadığını kontrol eden bir döngü yazabilirim. Ama sonra başka bir harita türü için kullanmak istediğimde bu çeki tekrar yazmam gerekiyor (örneğin map[string]string).

Sonunda yaptığım şey, haritaları dizelere dönüştürdüm ve dizeleri karşılaştırdım:

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

Bu, eşdeğer haritaların dizgi temsillerinin aynı olduğunu varsayar, bu durumda bu doğru görünür (eğer anahtarlar aynıysa, o zaman aynı değere karma yaparlar, bu nedenle sıraları aynı olacaktır). Bunu yapmanın daha iyi bir yolu var mı? Tablo tabanlı testlerde iki haritayı karşılaştırmanın deyimsel yolu nedir?


4
Hata, hayır: Bir haritayı yineleyen sıranın tahmin edilebilir olması garanti edilmez : "Haritalar üzerindeki yineleme sırası belirtilmemiştir ve bir yinelemeden diğerine aynı olacağı garanti edilmez. ..." .
zzzz

2
Ayrıca, belirli boyutlardaki haritalar için Go, sıralamayı kasıtlı olarak rastgele seçecektir. Bu düzene güvenmemeniz şiddetle tavsiye edilir.
Jeremy Duvar

Bir haritayı karşılaştırmaya çalışmak, programınızdaki bir tasarım hatasıdır.
İnanç Gümüş

4
Go 1.12 (Şubat 2019) ile, Haritalar artık testi kolaylaştırmak için anahtar sıralamasıyla yazdırıldı . Aşağıdaki cevabımı
VonC

Yanıtlar:


174

Go kitaplığı sizi zaten kapsıyor. Bunu yap:

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

Eğer bakarsak kaynak kodu için reflect.DeepEqualbireyin Mapdurumda daha önce nihayet onlar (anahtarın aynı kümesine sahip olmadığını görmek için kontrol aynı uzunlukta varsa, o zaman çek, hem haritalar eğer ilk kontroller nil olduğunu göreceksiniz değer) çiftleri.

Çünkü reflect.DeepEqualbir arayüz türünü dikkate alır hiç geçerli harita (üzerinde çalışacak map[string]bool, map[struct{}]interface{}, vs). Harita dışı değerlerde de çalışacağını unutmayın, bu nedenle ona ilettiğiniz şeyin gerçekten iki harita olmasına dikkat edin. İki tamsayı geçirirseniz, eşit olup olmadıklarını size memnuniyetle söyleyecektir.


Harika, tam da aradığım buydu. Sanırım jnml bunun performans gösterici olmadığını söylüyordu, ama bir test vakasında kimin umurunda.
andras

Evet, bunu bir üretim uygulaması için isterseniz, mümkünse kesinlikle özel olarak yazılmış bir işlevle giderim, ancak performans önemli değilse kesinlikle işe yarıyor.
joshlf

1
@andras Ayrıca gocheck'e de bakmalısınız . Kadar basit c.Assert(m1, DeepEquals, m2). Bunun güzel yanı, testi iptal etmesi ve size neye sahip olduğunuzu ve çıktıdan ne beklediğinizi söylemesidir.
Luke

8
DeepEqual'in ayrıca dilimlerin SİPARİŞ'inin eşit olmasını gerektirdiğini belirtmek gerekir .
Xeoncross


13

Tablo tabanlı testlerde iki haritayı karşılaştırmanın deyimsel yolu nedir?

Yardımcı olacak bir projeniz var go-test/deep.

Ama: bununla daha kolay olmalı Go 1.12 (2019 Şubat) yerel olarak : Bkz sürüm notları .

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

fmt

Haritalar, testi kolaylaştırmak için artık anahtar sıralı olarak yazdırılıyor .

Sipariş kuralları şunlardır:

  • Mümkün olduğunda, sıfır düşük
  • ints, floats ve string sıralama ölçütü <
  • NaN, NaN olmayan şamandıralardan daha azını karşılaştırır
  • booldaha falseönce karşılaştırırtrue
  • Karmaşık, önce gerçek sonra hayali karşılaştırır
  • İşaretçiler makine adresine göre karşılaştırır
  • Kanal değerleri makine adresine göre karşılaştırılır
  • Yapılar sırayla her alanı karşılaştırır
  • Diziler sırayla her bir öğeyi karşılaştırır
  • Arayüz değerleri, önce reflect.Typesomut türü tanımlayarak ve ardından önceki kurallarda açıklandığı gibi somut değerle karşılaştırılır.

Haritaları yazdırırken, NaN gibi dönüşlü olmayan anahtar değerleri önceden olarak görüntüleniyordu <nil>. Bu sürümden itibaren doğru değerler yazdırılır.

Kaynaklar:

CL şunu ekler: ( CL, "Listeyi Değiştir" anlamına gelir )

Bunu yapmak için, köke,internal/fmtsort türüne bakılmaksızın harita anahtarlarını sıralamak için genel bir mekanizma uygulayan bir paket ekliyoruz .

Bu biraz dağınık ve muhtemelen yavaştır, ancak haritaların biçimlendirilmiş basımı hiç hızlı olmamıştı ve zaten her zaman yansıma odaklı.

Yeni paket içseldir çünkü herkesin bunu bir şeyleri sıralamak için kullanmasını gerçekten istemiyoruz. Yavaştır, genel değildir ve yalnızca eşleme anahtarları olabilecek türlerin alt kümesi için uygundur.

Ayrıca text/template, bu mekanizmanın daha zayıf bir versiyonuna sahip olan paketi de kullanın .

Bunun kullanıldığını görebilirsin src/fmt/print.go#printValue(): case reflect.Map:


Cehaletim için üzgünüm, Go'da yeniyim, ancak bu yeni fmtdavranış haritaların eşdeğerliğini test etmeye tam olarak nasıl yardımcı oluyor? Kullanmak yerine dize temsillerini karşılaştırmayı mı öneriyorsunuz DeepEqual?
sschuberth

@sschuberth DeepEqualhala iyi. (veya daha doğrusucmp.Equal ) Kullanım durumu daha çok twitter.com/mikesample/status/1084223662167711744'te , orijinal sayıda belirtilen farklı günlükler gibi gösterilmiştir: github.com/golang/go/issues/21095 . Anlamı: Testinizin niteliğine bağlı olarak, güvenilir bir fark yardımcı olabilir.
VonC

fmt.Sprint(map1) == fmt.Sprint(map2)tl için; dr
425nesp

@ 425nesp Teşekkürler. Cevabı buna göre düzenledim.
VonC

11

Yapacağım şey bu (denenmemiş kod):

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
}

Tamam, ama örneklerini karşılaştırmak istediğim başka bir test olayım var map[string]float64. eqyalnızca map[string]intharitalar için çalışır . eqYeni bir harita türünün örneklerini her karşılaştırmak istediğimde , işlevin bir sürümünü uygulamalı mıyım ?
andras

@andras: 11 SLOC. Bunu sormak için gerekenden daha kısa sürede "kopyala yapıştır" konusunda uzmanlaşırdım. Yine de, diğerleri aynı şeyi yapmak için "yansıtmayı" kullanır, ancak bu çok daha kötü bir performansa sahiptir.
zzzz

1
Bu, haritaların aynı sırada olmasını beklemiyor mu? Blog.golang.org/go-maps-in-action
nathj07

3
@ nathj07 Hayır, çünkü sadece üzerinden yineleme yapıyoruz a.
Torsten Bronger

5

Sorumluluk reddi beyanı: map[string]intSorunun başlığı olan Go'daki haritaların eşdeğerliğinin test edilmesiyle ilgili değil ancak bununla ilgili

Eğer bir işaretçi tipi bir harita varsa (gibi map[*string]int), o zaman do not reflect.DeepEqual kullanmak istiyorum o return false çünkü.

Son olarak, anahtar time.Time gibi aktarılmamış bir işaretçi içeren bir türse, bu tür bir haritada yansıtın.DeepEqual da false döndürebilir .


3

Github.com/google/go-cmp/cmp "Diff" yöntemini kullanın :

Kod:

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

Çıktı:

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

Bunun yerine cmp ( https://github.com/google/go-cmp ) kullanın:

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

Başarısız test

Beklenen çıktınızdaki harita "sıralaması" işlevinizin döndürdüğü şey değilse yine de başarısız olur. Ancak cmpyine de tutarsızlığın nerede olduğuna işaret edebiliyor.

Referans için bu tweet'i buldum:

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

"yansıtma.DeepEqual'ı testlerde kullanmak genellikle kötü bir fikirdir, bu nedenle kaynaklı http://github.com/google/go-cmp açıyoruz " - Joe Tsai


1

En basit yol:

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

Misal:

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

Seçeneklerden biri, rng'yi düzeltmektir:

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

Affedersiniz ama cevabınız bu soruyla nasıl ilgili?
Dima Kozhevin

@DimaKozhevin golang, bir haritadaki girişlerin sırasını karıştırmak için dahili olarak rng kullanır. Rng'yi düzeltirseniz, test amacıyla tahmin edilebilir bir sipariş alırsınız.
Grozz

@Grozz Öyle mi? Neden!? Olabilir diye mutlaka tartışmıyorum (hiçbir fikrim yok) Sadece neden olacağını anlamıyorum.
msanford

Golang üzerinde çalışmıyorum, bu yüzden onların mantığını açıklayamam, ancak bu en azından v1.9 itibariyle onaylanmış bir davranış. Ancak, "haritalarda sipariş vermeye güvenemeyeceğinizi zorlamak istiyoruz, çünkü yapmamalısınız" şeklinde bir açıklama gördüm.
Grozz
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.