จะทดสอบความเท่าเทียมกันของแผนที่ใน 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)
}

สิ่งนี้ถือว่าการแทนค่าสตริงของแผนที่ที่เทียบเท่านั้นเหมือนกันซึ่งดูเหมือนว่าจะเป็นจริงในกรณีนี้ (หากคีย์เหมือนกันก็จะแฮชเป็นค่าเดียวกันดังนั้นคำสั่งของพวกเขาจะเหมือนกัน) มีวิธีที่ดีกว่านี้หรือไม่? วิธีการเปรียบเทียบสองแผนที่ในการทดสอบที่ขับเคลื่อนด้วยตารางเป็นสำนวนอย่างไร


4
เอ่อไม่มีคำสั่งวนแผนที่ไม่ได้รับประกันว่าจะสามารถคาดเดาได้ : "การสั่งซื้อซ้ำกว่าแผนที่ไม่ได้ระบุและไม่รับประกันว่าจะต้องเหมือนกันจากที่หนึ่งไปยังย้ำต่อไป ... ."
zzzz

2
นอกจากนี้สำหรับแผนที่บางขนาด Go จะสุ่มลำดับโดยเจตนา ไม่แนะนำให้ขึ้นอยู่กับคำสั่งนั้น
Jeremy Wall

การพยายามเปรียบเทียบแผนที่เป็นข้อบกพร่องในการออกแบบโปรแกรมของคุณ
Inanc Gumus

4
ทราบว่ามีการเดินทาง 1.12 (กุมภาพันธ์ 2019) แผนที่จะพิมพ์ตอนในการสั่งซื้อที่สำคัญเรียงลำดับเพื่อความสะดวกในการทดสอบ ดูคำตอบของฉันด้านล่าง
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.DeepEqualของ 's Mapคุณจะเห็นว่าก่อนอื่นจะตรวจสอบว่าทั้งสองแผนที่เป็นศูนย์หรือไม่จากนั้นจะตรวจสอบว่ามีความยาวเท่ากันหรือไม่ก่อนที่จะตรวจสอบว่ามีชุดเดียวกัน (key, มูลค่า) คู่

เนื่องจากreflect.DeepEqualใช้ประเภทอินเทอร์เฟซจึงทำงานบนแผนที่ที่ถูกต้อง ( map[string]bool, map[struct{}]interface{}ฯลฯ ) โปรดทราบว่ามันจะทำงานกับค่าที่ไม่ใช่แผนที่ด้วยดังนั้นโปรดระวังว่าสิ่งที่คุณส่งผ่านไปนั้นเป็นแผนที่สองแผนที่จริงๆ ถ้าคุณผ่านมันไปสองจำนวนเต็มมันจะบอกคุณได้อย่างมีความสุขว่ามันเท่ากันหรือไม่


ยอดเยี่ยมนั่นคือสิ่งที่ฉันกำลังมองหา ฉันเดาว่า jnml บอกว่าไม่ใช่นักแสดง แต่ใครจะสนใจในกรณีทดสอบ
andras

ใช่ถ้าคุณต้องการสิ่งนี้สำหรับแอปพลิเคชันการผลิตฉันจะไปกับฟังก์ชันที่เขียนขึ้นเองอย่างแน่นอนหากเป็นไปได้ แต่นี่จะเป็นเคล็ดลับอย่างแน่นอนหากประสิทธิภาพไม่น่ากังวล
joshlf

1
@andras คุณควรตรวจสอบgocheckด้วย ง่ายเหมือนc.Assert(m1, DeepEquals, m2). สิ่งที่ดีเกี่ยวกับสิ่งนี้คือยกเลิกการทดสอบและบอกคุณว่าคุณได้อะไรและสิ่งที่คุณคาดหวังในผลลัพธ์
Luke

8
มันน่าสังเกตว่า DeepEqual ยังต้องสั่งซื้อของชิ้นจะเท่ากับ
Xeoncross


13

วิธีการเปรียบเทียบสองแผนที่ในการทดสอบที่ขับเคลื่อนด้วยตารางเป็นสำนวนอย่างไร

คุณมีโครงการที่go-test/deepจะช่วย

แต่: นี้ควรจะง่ายขึ้นด้วยการไป 1.12 (กุมภาพันธ์ 2019) กำเนิด : ดูบันทึกประจำรุ่น

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

fmt

แผนที่จะมีการพิมพ์ในขณะนี้ในการสั่งซื้อที่สำคัญเรียงเพื่อความสะดวกในการทดสอบ

กฎการสั่งซื้อคือ:

  • เมื่อทำได้ศูนย์จะเปรียบเทียบค่าต่ำ
  • ints ลอยและสตริงเรียงตาม <
  • NaN เปรียบเทียบน้อยกว่าการลอยตัวที่ไม่ใช่ NaN
  • boolเปรียบเทียบfalseก่อนtrue
  • คอมเพล็กซ์เปรียบเทียบของจริงตามด้วยจินตภาพ
  • ตัวชี้เปรียบเทียบตามที่อยู่เครื่อง
  • ค่าช่องจะเปรียบเทียบตามที่อยู่เครื่อง
  • โครงสร้างเปรียบเทียบแต่ละฟิลด์ในทางกลับกัน
  • อาร์เรย์เปรียบเทียบแต่ละองค์ประกอบในทางกลับกัน
  • ค่าอินเทอร์เฟซจะเปรียบเทียบก่อนโดยการreflect.Typeอธิบายประเภทคอนกรีตจากนั้นตามมูลค่าคอนกรีตตามที่อธิบายไว้ในกฎก่อนหน้า

<nil>เมื่อพิมพ์แผนที่ค่าคีย์ที่ไม่สะท้อนเช่นน่านมีการแสดงผลก่อนหน้านี้เป็น ในรุ่นนี้จะมีการพิมพ์ค่าที่ถูกต้อง

แหล่งที่มา:

CL เพิ่ม: ( CL ย่อมาจาก "Change List" )

ในการทำเช่นนี้เราเพิ่มแพ็กเกจที่รูinternal/fmtsortทซึ่งใช้กลไกทั่วไปในการจัดเรียงคีย์แผนที่โดยไม่คำนึงถึงประเภท

สิ่งนี้ค่อนข้างยุ่งเล็กน้อยและอาจช้า แต่การพิมพ์แผนที่ในรูปแบบไม่เคยเร็วและขับเคลื่อนด้วยการสะท้อนแสงอยู่แล้ว

แพคเกจใหม่เป็นแบบภายในเพราะเราไม่ต้องการให้ทุกคนใช้สิ่งนี้เพื่อจัดเรียงสิ่งต่างๆ มันช้าไม่ใช่ทั่วไปและเหมาะสำหรับชุดย่อยของประเภทที่สามารถเป็นคีย์แผนที่ได้เท่านั้น

ใช้แพ็คเกจในtext/templateซึ่งมีกลไกนี้เวอร์ชันที่อ่อนแอกว่าอยู่แล้ว

คุณสามารถดูที่ใช้ใน src/fmt/print.go#printValue(): case reflect.Map:


ขออภัยในความไม่รู้ฉันเพิ่งเริ่มใช้ Go แต่fmtพฤติกรรมใหม่นี้ช่วยทดสอบความเท่าเทียมกันของแผนที่ได้อย่างไร คุณแนะนำให้เปรียบเทียบการแสดงสตริงแทนการใช้DeepEqualหรือไม่?
sschuberth

@sschuberth DeepEqualก็ยังดี. (หรือมากกว่าcmp.Equal ) กรณีการใช้งานที่มีการแสดงให้เห็นมากขึ้นในการtwitter.com/mikesample/status/1084223662167711744เช่น diffing บันทึกตามที่ระบุไว้ในฉบับเดิม: 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
}

ตกลง แต่ฉันมีกรณีทดสอบอื่นที่ฉันต้องการเปรียบเทียบอินสแตนซ์ของ map[string]float64. eqใช้ได้กับmap[string]intแผนที่เท่านั้น ฉันควรใช้เวอร์ชันของeqฟังก์ชันทุกครั้งที่ต้องการเปรียบเทียบอินสแตนซ์ของแผนที่ประเภทใหม่หรือไม่
andras

@andras: 11 SLOCs. ฉันจะ "คัดลอกวาง" ในเวลาอันสั้นกว่าที่จะถามเกี่ยวกับเรื่องนี้ แม้ว่าคนอื่น ๆ จะใช้ "สะท้อน" ในการทำเช่นเดียวกัน แต่ก็มีประสิทธิภาพที่แย่กว่ามาก
zzzz

1
ไม่ได้คาดหวังว่าแผนที่จะอยู่ในลำดับเดียวกัน? ไปไหนไม่รับประกันดู "ลำดับการทำซ้ำ" ในblog.golang.org/go-maps-in-action
nathj07

3
@ nathj07 aไม่มีเพราะเราย้ำเพียงผ่าน
Torsten Bronger

5

ข้อจำกัดความรับผิดชอบ : ไม่เกี่ยวข้องmap[string]intแต่เกี่ยวข้องกับการทดสอบความเท่าเทียมกันของแผนที่ใน Go ซึ่งเป็นชื่อคำถาม

ถ้าคุณมีแผนที่ประเภทตัวชี้ (เช่นmap[*string]int) แล้วคุณจะไม่ต้องการที่จะใช้ reflect.DeepEqualเพราะมันจะกลับเท็จ

สุดท้ายหากคีย์เป็นประเภทที่มีตัวชี้ที่ไม่ได้ส่งออกเช่นเวลา Time ให้สะท้อนถึง DeepEqual บนแผนที่ดังกล่าวก็สามารถส่งคืนเท็จได้เช่นกัน


3

ใช้เมธอด "Diff" ของgithub.com/google/go-cmp/cmp :

รหัส:

// 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=th

"การใช้ 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

หนึ่งในตัวเลือกคือการแก้ไข rng:

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

ขอโทษนะ แต่คำตอบของคุณเกี่ยวข้องกับคำถามนี้อย่างไร
Dima Kozhevin

@DimaKozhevin golang ภายในใช้ rng เพื่อผสมลำดับของรายการในแผนที่ หากคุณแก้ไข rng คุณจะได้รับคำสั่งที่คาดเดาได้สำหรับวัตถุประสงค์ในการทดสอบ
Grozz

@Grozz มันไม่? ทำไม!? ฉันไม่จำเป็นต้องโต้แย้งว่ามันอาจจะ (ฉันไม่รู้) ฉันไม่เห็นว่าทำไมถึงเป็นเช่นนั้น
msanford

ฉันไม่ได้ทำงานกับ Golang ดังนั้นฉันจึงไม่สามารถอธิบายเหตุผลของพวกเขาได้ แต่นั่นเป็นพฤติกรรมที่ได้รับการยืนยันอย่างน้อยก็ใน v1.9 อย่างไรก็ตามฉันเห็นคำอธิบายบางส่วนตามบรรทัดของ "เราต้องการบังคับว่าคุณไม่สามารถพึ่งพาการสั่งซื้อในแผนที่ได้เพราะคุณไม่ควร"
Grozz
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.