मुख्य सामग्री पर जाएं
मॉड्यूल 9 · पाठ 2 का 5पाठ्यक्रम में 42/50~14 min
मॉड्यूल पाठ (2/5)

टेबल-संचालित परीक्षण

टेबल-संचालित परीक्षण गो के मुहावरेदार पैटर्न हैं: एन अलग TestThisSpecificCase फ़ंक्शन लिखने के बजाय, आप मामलों का एक टुकड़ा घोषित करते हैं और पुनरावृत्त करते हैं। एक अभिकथन ब्लॉक है, और परीक्षण डेटा को तर्क से अलग किया गया है।

आधार पैटर्न

Go
func TestAbs(t *testing.T) {
    cases := []struct {
        name string
        in   int
        want int
    }{
        {"pos", 3, 3},
        {"neg", -3, 3},
        {"zero", 0, 0},
    }
    for _, tc := range cases {
        t.Run(tc.name, func(t *testing.T) {
            if got := Abs(tc.in); got != tc.want {
                t.Errorf("Abs(%d) = %d; voglio %d", tc.in, got, tc.want)
            }
        })
    }
}

तीन सामग्री:

  1. अनाम संरचना का टुकड़ा सामान्य फ़ील्ड के साथ: name, इनपुट, अपेक्षित आउटपुट (और शायद एक अपेक्षित त्रुटि)।
  2. for _, tc := range cases - निष्पादन लूप।
  3. t.Run(tc.name, func(t *testing.T) { ... }) - प्रत्येक मामला अपने नाम के साथ एक उप-परीक्षण बन जाता है।

t.Run क्यों?

t.Run एक उप-परीक्षण बनाता है जिसमें:

  • व्यक्तिगत नाम आउटपुट में दिखाया गया है: TestAbs/pos, TestAbs/neg, ...
  • इसका अपना t *testing.T: एक मामले में विफलता दूसरों को नहीं रोकती है (जब तक t.Fatal का उपयोग नहीं किया जाता है)।
  • कमांड-लाइन फ़िल्टरिंग: go test -run TestAbs/neg केवल वही केस चलाता है।
  • वैकल्पिक समानता: समानांतर करने के लिए फंक के अंदर t.Parallel() को कॉल करें।
Bash
go test -v -run TestAbs/neg

अपेक्षित त्रुटियों वाले मामले

Go
cases := []struct {
    name    string
    in      string
    want    int
    wantErr bool
}{
    {"ok", "42", 42, false},
    {"bad", "x", 0, true},
}
for _, tc := range cases {
    t.Run(tc.name, func(t *testing.T) {
        got, err := Atoi(tc.in)
        if (err != nil) != tc.wantErr {
            t.Fatalf("err = %v, wantErr = %v", err, tc.wantErr)
        }
        if !tc.wantErr && got != tc.want {
            t.Errorf("got %d, want %d", got, tc.want)
        }
    })
}

लूप बंद होने का ख़तरा (प्री-गो 1.22)

गो 1.22 से पहले के संस्करणों में, tc वैरिएबल को सभी पुनरावृत्तियों में साझा किया गया था: t.Run के अंदर t.Parallel() को कॉल करने से सभी उप-परीक्षण उसी अंतिम मामले के विरुद्ध चलने लगे। समाधान यह था:

Go
for _, tc := range cases {
    tc := tc // local rebind (pre-1.22)
    t.Run(tc.name, func(t *testing.T) {
        t.Parallel()
        // ...
    })
}

मामलों का नामकरण

सर्वोत्तम प्रथाएं:

  • एक संक्षिप्त लेकिन विचारोत्तेजक नाम: "empty input", "too large", "negative"।
  • यदि आप -run (या अंडरस्कोर का उपयोग करें) के साथ आसानी से फ़िल्टर करना चाहते हैं तो कोई रिक्त स्थान नहीं है।
  • हमेशा एक "हैप्पी पाथ" केस, एक "एज" केस (शून्य, खाली) और कम से कम एक त्रुटि केस शामिल करें।

अभ्यास

व्यायाम#go.m9.l2.e1
प्रयास: 0लोड हो रहा है...

एब्स (सकारात्मक, नकारात्मक, शून्य) का परीक्षण करने के लिए 3 मामलों से भरे हुए फ़ील्ड के साथ अज्ञात संरचना के एक केस स्लाइस को परिभाषित करें।

संपादक लोड हो रहा है...

3 प्रयासों के बाद समाधान उपलब्ध है

व्यायाम#go.m9.l2.e2
प्रयास: 0लोड हो रहा है...

प्रत्येक मामले को नामित उप-परीक्षण में बदलने के लिए t.Run को tc.name के साथ जोड़ें।

संपादक लोड हो रहा है...

3 प्रयासों के बाद समाधान उपलब्ध है

Quiz#go.m9.l2.e3
तैयार

एन अलग-अलग टेस्ट फ़ंक्शंस की तुलना में टेबल-संचालित पैटर्न का मुख्य लाभ क्या है?

Go
for _, tc := range cases {
  t.Run(tc.name, func(t *testing.T) { ... })
}
उत्तर विकल्प