Go REST हैंडलर्स का परीक्षण: httptest और टेबल-ड्रिवन चेक्स
httptest और टेबल-ड्रिवन केस के साथ Go REST हैंडलर्स का परीक्षण रिलीज़ से पहले ऑथ, वेलिडेशन, स्टेटस कोड और एज-केस दोहराने योग्य तरीके से जाँचने देता है।

रिलीज़ से पहले जिन बातों पर आपको भरोसा होना चाहिए
एक REST हैंडलर कंपाइल हो सकता है, एक त्वरित मैन्युअल चेक पास कर सकता है, और फिर भी प्रोडक्शन में फेल हो सकता है। ज़्यादातर फेल्यर सिंटैक्स की वजह से नहीं होते। ये कॉन्ट्रैक्ट की समस्या होते हैं: हैंडलर वो स्वीकार कर लेता है जिसे उसे रिजेक्ट करना चाहिए था, गलत स्टेटस कोड लौटाता है, या एरर में संवेदनशील डिटेल्स लीक कर देता है।
मैन्युअल टेस्टिंग मदद करती है, लेकिन एज केस और रिग्रेशन मिस होना आसान है। आप एक हैप्पी पाथ ट्राई करते हैं, शायद एक स्पष्ट एरर, और आगे बढ़ जाते हैं। फिर वेलिडेशन या मिडलवेयर में छोटा सा बदलाव चुपचाप उस व्यवहार को तोड़ देता है जिसे आपने स्थिर माना था।
हैंडलर टेस्ट का लक्ष्य सरल है: हैंडलर के वादों को दोहराने योग्य बनाना। इसमें ऑथ नियम, इनपुट वेलिडेशन, पूर्वानुमेय स्टेटस कोड, और ऐसे एरर बॉडीज़ शामिल हैं जिन पर क्लाइंट भरोसा कर सकते हैं।
Go का httptest पैकेज इस काम के लिए अच्छा है क्योंकि आप बिना असली सर्वर शुरू किए हैंडलर को डायरेक्टली चलाकर रिस्पॉन्स बॉडी, हेडर्स और स्टेटस कोड जांच सकते हैं। टेस्ट तेज़, अलग-थलग, और हर कमिट पर चलाने में आसान रहते हैं।
रिलीज़ से पहले आपके पास होना चाहिए (उम्मीद नहीं, भरोसा):
- ऑथ व्यवहार लगातार हो: missing token, invalid token, और गलत रोल के मामलों में एक जैसा व्यवहार।
- इनपुट वैलिडेट होते हों: required फील्ड्स, टाइप्स, रेंज, और (यदि आप लागू करते हैं) अज्ञात फील्ड्स।
- स्टेटस कोड कॉन्ट्रैक्ट से मेल खाते हों (उदाहरण के लिए 401 बनाम 403, 400 बनाम 422)।
- एरर रिस्पॉन्स सुरक्षित और स्थिर हों (कोई स्टैक ट्रेस नहीं, हर बार एक जैसा शेप)।
- नॉन-हैप्पी पाथ्स संभाले गए हों: टाइमआउट्स, डाउनस्ट्रीम फेल्यर, और खाली रिज़ल्ट्स।
"टिकट बनाएं" एंडपॉइंट तब काम कर सकता है जब आप परफेक्ट JSON एक एडमिन के रूप में भेजते हैं। टेस्ट पकड़ेगा वो चीज़ें जो आप भूल जाते हैं आज़माना: एक एक्सपायर्ड टोकन, क्लाइंट द्वारा गलती से भेजा गया अतिरिक्त फ़ील्ड, नकारात्मक प्राथमिकता, या निर्भरता फेल होने पर "नॉट फाउंड" और "इंटरनल एरर" के बीच का अंतर।
हर एंडपॉइंट के लिए कॉन्ट्रैक्ट परिभाषित करें
टेस्ट लिखने से पहले हैंडलर क्या वादा करता है यह लिखें। एक स्पष्ट कॉन्ट्रैक्ट टेस्ट को फोकस्ड रखता है और उन्हें कोड के "मतलब" के अनुमान में बदलने से रोकता है। यह रिफैक्टरिंग को भी सुरक्षित बनाता है क्योंकि आप इंटर्नल बदल सकते हैं बिना बिहेवियर बदले।
इनपुट से शुरू करें। यह स्पष्ट करें कि हर वैल्यू कहाँ से आती है और क्या आवश्यक है। एक एंडपॉइंट path से id, query string से limit, Authorization हेडर, और JSON बॉडी ले सकता है। जिन नियमों की परवाह है उन्हें नोट करें: अलाउड फॉर्मैट, मिन/मैक्स वैल्यूज़, required फील्ड्स, और कुछ मिस होने पर क्या होता है।
फिर आउटपुट परिभाषित करें। केवल "JSON लौटाता है" तक न रुकें। डिसाइड करें कि सक्सेस क्या दिखता है, कौन से हेडर मायने रखते हैं, और एरर कैसे दिखते हैं। अगर क्लाइंट्स स्थिर एरर कोड और प्रेडिक्टेबल JSON शेप पर निर्भर करते हैं, तो उसे कॉन्ट्रैक्ट का हिस्सा मानें।
एक व्यावहारिक चेकलिस्ट:
- इनपुट: path/query वैल्यूज़, आवश्यक हेडर, JSON फ़ील्ड्स, और वेलिडेशन नियम
- आउटपुट: स्टेटस कोड, रिस्पॉन्स हेडर, सक्सेस और एरर दोनों के लिए JSON शेप
- साइड इफेक्ट्स: क्या डेटा बदलता है और क्या क्रिएट होता है
- निर्भरताएँ: डेटाबेस कॉल, बाहरी सर्विसेज, करंट टाइम, जनरेटेड IDs
यह भी तय करें कि हैंडलर टेस्ट कहाँ पर रुकते हैं। हैंडलर टेस्ट HTTP बॉर्डर पर सबसे मजबूत होते हैं: ऑथ, पार्सिंग, वेलिडेशन, स्टेटस कोड, और एरर बॉडीज़। गहरे मामलों को इंटीग्रेशन टेस्ट में डालें: असली DB क्वेरीज़, नेटवर्क कॉल, और पूरा राउटिंग।
अगर आपका बैकएंड जनरेट होता है (उदाहरण के लिए, AppMaster Go हैंडलर्स और बिज़नेस लॉजिक बनाता है), तो कॉन्ट्रैक्ट-फर्स्ट अप्रोच और भी उपयोगी है। आप कोड को फिर से जनरेट कर सकते हैं और फिर भी हर एंडपॉइंट का पब्लिक बिहेवियर वेरिफाई कर सकते हैं।
एक न्यूनतम httptest हार्नेस सेटअप करें
एक अच्छा हैंडलर टेस्ट असली रिक्वेस्ट भेजने जैसा महसूस होना चाहिए, बिना सर्वर स्टार्ट किए। Go में यह आमतौर पर मतलब है: httptest.NewRequest से रिक्वेस्ट बनाना, httptest.NewRecorder से रिस्पॉन्स कैप्चर करना, और अपने हैंडलर को कॉल करना।
हैंडलर को डायरेक्ट कॉल करने से टेस्ट तेज और फोकस्ड रहते हैं। यह आदर्श है जब आप हैंडलर के अंदर व्यवहार वेरिफाई करना चाहते हैं: ऑथ चेक, वेलिडेशन नियम, स्टेटस कोड, और एरर बॉडीज़। कॉन्ट्रैक्ट पाथ में अगर path params, route matching, या मिडलवेयर ऑर्डर मायने रखता है तो टेस्ट में router का उपयोग मददगार है। डायरेक्ट कॉल से शुरू करें और केवल जब ज़रूरत हो तब router जोड़ें।
हेडर्स ज़्यादा मायने रखते हैं जितना लोग सोचते हैं। एक मिसिंग Content-Type यह बदल सकता है कि हैंडलर बॉडी कैसे पढ़ता है। हर केस में उन हेडर्स को सेट करें जिन्हें आप उम्मीद करते हैं ताकि फेल्योर लॉजिक की वजह से हों न कि टेस्ट सेटअप की वजह से।
यहाँ एक न्यूनतम पैटर्न है जिसे आप फिर से उपयोग कर सकते हैं:
req := httptest.NewRequest(http.MethodPost, "/v1/widgets", strings.NewReader(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
res := rec.Result()
defer res.Body.Close()
असबर्ज़न को कॉन्सिस्टेंट रखने के लिए एक छोटा हेल्पर उपयोगी होता है जो रिस्पॉन्स बॉडी को पढ़कर डिकोड करे। अधिकांश टेस्ट में पहले स्टेटस कोड चेक करें (ताकि फेल्योर स्कैन करना आसान हो), फिर उन की-हेडर्स को जिन्होंने वादा किया है (अक्सर Content-Type), और फिर बॉडी।
अगर आपका बैकएंड जनरेटेड है (जिसमें AppMaster द्वारा उत्पादित Go बैकेंड भी शामिल है), यह हार्नेस अभी भी लागू होता है। आप HTTP कॉन्ट्रैक्ट टेस्ट कर रहे हैं जिस पर यूज़र्स निर्भर हैं, न कि कोड स्टाइल पर।
पठनीय बने रहने वाले टेबल-ड्रिवन केस डिजाइन करें
टेबल-ड्रिवन टेस्ट्स तब सबसे अच्छे होते हैं जब हर केस एक छोटी कहानी की तरह पढ़े: आप जो रिक्वेस्ट भेजते हैं और आप क्या उम्मीद करते हैं। आपको फ़ाइल में इधर-उधर जाने की ज़रूरत पड़े बिना टेबल स्कैन करके कवरेज समझ में आ जाना चाहिए।
एक ठोस केस में आमतौर पर होता है: एक स्पष्ट नाम, रिक्वेस्ट (method, path, headers, body), अपेक्षित स्टेटस कोड, और रिस्पॉन्स के लिए एक चेक। JSON बॉडीज़ के लिए, पूरी JSON स्ट्रिंग मैच करने की बजाय कुछ स्थिर फ़ील्ड्स (जैसे एरर कोड) असर्ट करना पसंद करें, जब तक कि आपका कॉन्ट्रैक्ट सख्त आउटपुट माँगता न हो।
एक साधारण केस शेल्प जिसे आप फिर से उपयोग कर सकते हैं
केस struct को फोकस्ड रखें। एक-ऑफ सेटअप को हेल्पर्स में डालें ताकि टेबल छोटी रहे।
type tc struct {
name string
method string
path string
headers map[string]string
body string
wantStatus int
wantBody string // substring or compact JSON
}
भिन्न इनपुट्स के लिए छोटे बॉडी स्ट्रिंग्स का उपयोग करें जो अंतर एक नज़र में दिखा दें: एक वैध पेलोड, एक मिसिंग फील्ड, एक गलत टाइप, और एक खाली स्ट्रिंग। टेबल में बहुत सारे फ़ॉर्मैटिंग वाले JSON बिल्ड करने से बचें—यह जल्दी शोर मचाता है।
जब आप रिपीट सेटअप देखें (टोकन क्रिएशन, सामान्य हेडर्स, डिफ़ॉल्ट बॉडी), उन्हें newRequest(tc) या baseHeaders() जैसे हेल्पर्स में धकेल दें।
अगर एक ही टेबल बहुत सी बातों को मिला रही हो तो उसे विभाजित करें। सक्सेस पाथ्स के लिए एक टेबल और एरर पाथ्स के लिए एक और टेबल अक्सर पढ़ने और डिबग करने में आसान होते हैं।
ऑथ चेक्स: वे केस जिन्हें अक्सर स्किप किया जाता है
ऑथ टेस्ट अक्सर हैप्पी पाथ पर ठीक दिखते हैं, फिर प्रोडक्शन में फेल होते हैं क्योंकि एक "छोटा" केस कभी एक्सरसाइज़ नहीं हुआ। ऑथ को एक कॉन्ट्रैक्ट की तरह ट्रीट करें: क्लाइंट क्या भेजता है, सर्वर क्या लौटाता है, और क्या कभी खुलना नहीं चाहिए।
टोकन की उपस्थिति और वैधता से शुरू करें। एक प्रोटेक्टेड एंडपॉइंट को तब अलग व्यवहार करना चाहिए जब हेडर मिसिंग हो बनाम मौजूद लेकिन गलत हो। अगर आप शॉर्ट-लाइव्ड टोकन यूज़ करते हैं तो expiry भी टेस्ट करें, भले ही आप इसे एक वैलिडेटर इंजेक्ट करके सिम्युलेट करें जो "expired" लौटाता हो।
अधिकांश गैप इन केसों से कवर होते हैं:
- No
Authorizationheader -> 401 के साथ स्थिर एरर रिस्पॉन्स - Malformed header (गलत प्रिफिक्स) -> 401
- Invalid token (खराब सिग्नेचर) -> 401
- Expired token -> 401 (या आपकी चुनी हुई कोड) एक प्रेडिक्टेबल मैसेज के साथ
- Valid token पर गलत रोल/अनुमतियाँ -> 403
401 बनाम 403 का फर्क मायने रखता है। 401 तब उपयोग करें जब कॉलर authenticated नहीं है। 403 तब उपयोग करें जब वे authenticated हैं पर अनुमति नहीं है। अगर आप इनको धुंधला कर देंगे तो क्लाइंट्स बेवजह retry करेंगे या गलत UI दिखेगा।
"यूजर-ओन्ड" एंडपॉइंट्स (जैसे GET /orders/{id}) पर सिर्फ रोल चेक पर्याप्त नहीं होते। ownership टेस्ट करें: user A को user B का ऑर्डर नहीं देखना चाहिए, भले ही उसके पास वैध टोकन हो। यह क्लीन 403 होना चाहिए (या 404, अगर आप अनजाने में अस्तित्व छुपाना चाहते हैं), और बॉडी कुछ भी लीक नहीं करनी चाहिए। एरर जेनरिक रखें—"ऑर्डर user 42 का है" जैसा हिंट न दें।
इनपुट नियम: स्पष्ट रूप से validate करें, reject करें और समझाएँ
कई प्री-रिलीज़ बग इनपुट बग होते हैं: मिसिंग फील्ड्स, गलत टाइप्स, अनपेक्षित फॉर्मैट्स, या बहुत बड़े पेलोड्स।
हर इनपुट को नाम दें जिसे आपका हैंडलर स्वीकार करता है: JSON बॉडी फ़ील्ड्स, क्वेरी पैरामीटर्स, और पाथ पैरामीटर्स। हर एक के लिए तय करें कि जब वह मिस, खाली, malformed, या रेंज से बाहर हो तो क्या होता है। फिर ऐसे केस लिखें जो यह प्रमाणित करें कि हैंडलर खराब इनपुट को जल्दी रिजेक्ट करता है और हर बार एक ही तरह की एरर लौटाता है।
एक छोटा सा वेलिडेशन केस सेट अधिकांश रिस्क कवर कर देता है:
- Required फील्ड्स: missing vs empty string vs null (यदि आप null की अनुमति देते हैं)
- Types और फॉर्मैट्स: नंबर बनाम स्ट्रिंग, email/date/UUID फॉर्मैट्स, boolean पार्सिंग
- साइज लिमिट्स: max length, max items, payload बहुत बड़ा
- Unknown फील्ड्स: ignore बनाम reject (यदि आप strict decoding लागू करते हैं)
- Query और path params: मिसिंग, पार्सेबल नहीं, और डिफ़ॉल्ट व्यवहार
उदाहरण: एक POST /users हैंडलर { "email": "...", "age": 0 } स्वीकार करता है। टेस्ट करें email मिसिंग, email के रूप में 123, email के रूप में "not-an-email", age के रूप में -1, और age के रूप में "20"। अगर आप strict JSON चाहते हैं, तो { "email":"[email protected]", "extra":"x" } भी टेस्ट करें और पुष्टि करें कि यह फेल होता है।
वेलिडेशन फेल्योर को प्रेडिक्टेबल बनाएं। वेलिडेशन एरर के लिए एक स्टेटस कोड चुनें (कुछ टीमें 400 यूज़ करती हैं, अन्य 422)। एरर बॉडी शेप को स्थिर रखें। टेस्ट दोनों स्टेटस और एक मैसेज (या details फ़ील्ड) असर्ट करें जो बताता हो कि किस इनपुट ने फेल किया।
स्टेटस कोड और एरर बॉडीज़: उन्हें predictable बनायें
हैंडलर टेस्ट आसान होते हैं जब API फेल्यर्स बोरिंग और कॉन्सिस्टेंट हों। आप चाहते हैं कि हर एरर स्पष्ट स्टेटस कोड से मैप हो और वही JSON शेप लौटाए, चाहे किसने भी हैंडलर लिखा हो।
एक छोटा, सहमत मैपिंग तय करें:
{ "code": "user_not_found", "message": "User was not found", "details": { "id": "123" } }
(ऊपर वाला JSON उदाहरण और अन्य fenced code ब्लॉक्स अनुवाद से अप्रभावित रहेंगे।)
आममैपिंग उदाहरण:
- 400 Bad Request: malformed JSON, missing required query params
- 404 Not Found: resource ID मौजूद नहीं है
- 409 Conflict: unique constraint या state conflict
- 422 Unprocessable Entity: वैध JSON पर भी बिज़नेस नियम फेल हो रहे हों
- 500 Internal Server Error: अनअपेक्षित फेल्यर (DB डाउन, nil pointer, तृतीय-पक्ष आउटेज)
फिर एरर बॉडी स्थिर रखें। भले ही मैसेज टेक्स्ट बाद में बदल जाए, क्लाइंट्स के पास भरोसेमंद फ़ील्ड्स होने चाहिए:
{ "code": "user_not_found", "message": "User was not found", "details": { "id": "123" } }
टेस्ट में शेप पर असर्ट करें, सिर्फ स्टेटस लाइन पर नहीं। एक आम समस्या यह है कि एरर पर HTML, प्लेन टेक्स्ट, या खाली बॉडी लौटाना—यह क्लाइंट्स को तोड़ देता है और बग्स छिपाता है।
एरर रिस्पॉन्स के हेडर्स और एन्कोडिंग भी टेस्ट करें:
Content-Typeдолжен бытьapplication/json(और यदि आप charset सेट करते हैं तो वह भी पहुँच में हो)- फेल्यर पर भी बॉडी वैध JSON हो
code,message, औरdetailsमौजूद हों (details खाली हो सकता है, लेकिन यादृच्छिक नहीं होना चाहिए)- पैनिक्स और अनअपेक्षित एरर सुरक्षित 500 लौटाएँ बिना स्टैक ट्रेस लीक किए
यदि आपने recover middleware जोड़ा है, तो एक टेस्ट शामिल करें जो पैनिक फोर्स करे और पुष्टि करे कि आपको फिर भी एक साफ़ JSON एरर रिस्पॉन्स मिलता है।
एज केस: फेल्यर, समय, और नॉन-हैप्पी पाथ्स
हैप्पी-पाथ टेस्ट यह साबित करते हैं कि हैंडलर एक बार काम करता है। एज-केस टेस्ट यह साबित करते हैं कि यह तब भी सही व्यवहार करता है जब दुनिया गड़बड़ हो।
डाउनस्ट्रीम कॉल्स को विशिष्ट, दोहराने योग्य तरीके से फेल करवाएँ। यदि आपका हैंडलर डेटाबेस, कैश, या बाहरी API को कॉल करता है, आप यह देखना चाहेंगे कि वे परतें उन एररज़ पर क्या प्रतिक्रिया देती हैं जिन्हें आप नियंत्रित नहीं करते।
इन मामलों को कम से कम एक बार प्रत्येक एंडपॉइंट के लिए सिम्युलेट करना चाहिए:
- डाउनस्ट्रीम कॉल से टाइमआउट (
context deadline exceeded) - स्टोरेज से Not Found जब क्लाइंट को डेटा की उम्मीद थी
- क्रिएट पर Unique constraint violation (duplicate email, duplicate slug)
- नेटवर्क या ट्रांसपोर्ट एरर (connection refused, broken pipe)
- अनअपेक्षित इंटरनल एरर (generic "something went wrong")
टेस्ट्स को स्थिर रखें by उन सभी चीज़ों को कंट्रोल करके जो रन के बीच बदल सकती हैं। flaky टेस्ट होने से बेहतर है कि टेस्ट ही न हों क्योंकि flaky टेस्ट लोगों को फेल्यर्स को नजरअंदाज़ करना सिखा देती है।
समय और रैंडमनेस को प्रेडिक्टेबल बनाएं
अगर हैंडलर time.Now(), IDs, या रैंडम वैल्यूज़ यूज़ करता है, उन्हें इंजेक्ट करें। हैंडलर या सर्विस में clock फंक्शन और ID जनरेटर पास करें। टेस्ट में फिक्स्ड वैल्यूज़ लौटाएँ ताकि आप सटीक JSON फ़ील्ड्स और हेडर्स को असर्ट कर सकें।
छोटे फेक्स का उपयोग करें, और "नो साइड इफेक्ट्स" असर्ट करें
पूरे मॉक के बजाय छोटे फेक्स या स्टब्स पसंद करें। एक फेक कॉल्स रिकॉर्ड कर सकता है और आपको सत्यापित करने देगा कि असफलता के बाद कुछ नहीं हुआ।
उदाहरण के लिए, "create user" हैंडलर में अगर DB insert unique constraint एरर से फेल हो रहा है, तो असर्ट करें कि स्टेटस कोड सही है, एरर बॉडी स्थिर है, और कोई welcome email नहीं भेजी गई। आपका फेक मेलर एक काउंटर (sent=0) दिखा सकता है जिससे फेल पाथ यह प्रमाणित कर सके कि साइड इफेक्ट्स ट्रिगर नहीं हुए।
सामान्य गलतियाँ जो हैंडलर टेस्ट को अविश्वसनीय बनाती हैं
हैंडलर टेस्ट अक्सर गलत कारण से फेल होते हैं। टेस्ट में बनाई गई रिक्वेस्ट असली क्लाइंट रिक्वेस्ट के समान नहीं होती। यह शोर वाले फेल्यर्स और गलत कॉन्फिडेंस की ओर ले जाता है।
एक सामान्य समस्या JSON भेजना है बिना उन हेडर्स के जो हैंडलर उम्मीद करता है। अगर आपका कोड Content-Type: application/json चेक करता है, तो उसे भूलने से हैंडलर JSON डिकोडिंग स्किप कर सकता है, अलग स्टेटस कोड लौटा सकता है, या ऐसा ब्रांच ले सकता है जो प्रोडक्शन में कभी नहीं होता। ऑथ के लिए भी यही है: मिसिंग Authorization हेडर invalid token से अलग केस है। उन्हें अलग केस बनाएं।
एक और जाल पूरी JSON रिस्पॉन्स को रॉ स्ट्रिंग के रूप में असर्ट करना है। फ़ील्ड ऑर्डर, spacing, या नए फील्ड्स जैसी छोटी बदलते चीज़ें टेस्ट तोड़ देती हैं भले ही API सही हो। बॉडी को एक struct या map[string]any में डिकोड करें, फिर जो मायने रखता है उसे असर्ट करें: स्टेटस, एरर कोड, मैसेज, और कुछ की-फील्ड्स।
टेस्ट तब भी अविश्वसनीय हो जाते हैं जब केसेस साझा म्यूटेबल स्टेट शेयर करते हैं। एक ही इन-मेमरी स्टोर, ग्लोबल वैरिएबल, या सिंगलटन राउटर को टेबल की पंक्तियों में पुन: उपयोग करने से केस के बीच डेटा लीक हो सकता है। हर टेस्ट केस क्लीन से शुरू होना चाहिए, या t.Cleanup में स्टेट रीसैट करें।
ऐसे पैटर्न जो आमतौर पर brittle टेस्ट का कारण बनते हैं:
- वही हेडर्स और एन्कोडिंग न भेजना जो असली क्लाइंट भेजता है
- पूर्ण JSON स्ट्रिंग असर्ट करना बजाय डिकोड कर के फ़ील्ड्स चेक करने के
- केस के बीच साझा डेटाबेस/कैश/ग्लोबल स्टेट का पुन: उपयोग
- ऑथ, वेलिडेशन, और बिज़नेस लॉजिक आशयों को एक बड़े टेस्ट में पैक करना
हर टेस्ट को फोकस्ड रखें। अगर एक केस फेल हो तो आपको सेकंडों में पता चलना चाहिए कि वह ऑथ था, इनपुट नियम थे, या एरर फ़ॉर्मैटिंग की समस्या थी।
एक त्वरित प्री-रिलीज़ चेकलिस्ट जो आप फिर से उपयोग कर सकते हैं
रिलीज़ से पहले, टेस्ट को दो चीजें साबित करनी चाहिए: एंडपॉइंट अपना कॉन्ट्रैक्ट फॉलो करता है, और यह सुरक्षित, प्रेडिक्टेबल तरीके से फेल होता है।
इनको टेबल-ड्रिवन केस के रूप में चलाएँ, और हर केस रिस्पॉन्स तथा किसी भी साइड-इफेक्ट का असर्ट करे:
- ऑथ: कोई टोकन नहीं, खराब टोकन, गलत रोल, सही रोल (और पुष्टि करें कि "गलत रोल" केस डिटेल्स नहीं लीक करता)
- इनपुट: आवश्यक फील्ड्स मिसिंग, गलत टाइप्स, बॉउंड्री साइज (min/max), अज्ञात फील्ड्स जिन्हें आप रिजेक्ट करना चाहते हैं
- आउटपुट: स्टेटस कोड, की हेडर्स (जैसे
Content-Type), आवश्यक JSON फील्ड्स, स्थिर एरर शेप - निर्भरताएँ: एक डाउनस्ट्रीम फेल्यर (DB, queue, payment, email) फोर्स करें, सुरक्षित मैसेज वेरिफाई करें, आंशिक राइट्स न होने की पुष्टि करें
- आइडेम्पोटेंसी: वही रिक्वेस्ट दोहराएँ (या टाइमआउट के बाद retry करें) और पुष्टि करें कि आप डुप्लिकेट नहीं बना रहे
उसके बाद एक सनिटी असर्शन जोड़ें जो अक्सर स्किप किया जाता है: पुष्टि करें कि हैंडलर ने वह चीज़ छुई भी नहीं जिसे उसे नहीं छूना चाहिए। उदाहरण के लिए, वेलिडेशन फेल केस में सत्यापित करें कि कोई रिकॉर्ड नहीं बना और कोई ईमेल नहीं भेजी गई।
अगर आप AppMaster जैसे टूल से APIs बनाते हैं, तो यही चेकलिस्ट लागु रहती है। बिंदु यही है: पब्लिक बिहेवियर को स्थिर रखें।
उदाहरण: एक एंडपॉइंट, एक छोटी टेबल, और जो यह पकड़ता है
मान लीजिए आपके पास एक सरल एंडपॉइंट है: POST /login। यह JSON स्वीकार करता है जिसमें email और password हों। सक्सेस पर यह 200 के साथ टोकन लौटाता है, invalid input पर 400, गलत क्रेडेंशियल पर 401, और ऑथ सर्विस डाउन होने पर 500।
एक कॉम्पैक्ट टेबल जैसा नीचे दिया गया अधिकांश प्रोडक्शन में टूटने वाली चीज़ों को कवर कर देता है।
func TestLoginHandler(t *testing.T) {
// Fake dependency so we can force 200/401/500 without hitting real systems.
auth := &FakeAuth{ /* configure per test */ }
h := NewLoginHandler(auth)
tests := []struct {
name string
body string
authHeader string
setup func()
wantStatus int
wantBody string
}{
{"success", `{"email":"[email protected]","password":"secret"}`, "", func() { auth.Mode = "ok" }, 200, `"token"`},
{"missing password", `{"email":"[email protected]"}`, "", func() { auth.Mode = "ok" }, 400, "password"},
{"bad email format", `{"email":"not-an-email","password":"secret"}`, "", func() { auth.Mode = "ok" }, 400, "email"},
{"invalid JSON", `{`, "", func() { auth.Mode = "ok" }, 400, "invalid JSON"},
{"unauthorized", `{"email":"[email protected]","password":"wrong"}`, "", func() { auth.Mode = "unauthorized" }, 401, "unauthorized"},
{"server error", `{"email":"[email protected]","password":"secret"}`, "", func() { auth.Mode = "error" }, 500, "internal"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.setup()
req := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(tt.body))
req.Header.Set("Content-Type", "application/json")
if tt.authHeader != "" {
req.Header.Set("Authorization", tt.authHeader)
}
rr := httptest.NewRecorder()
h.ServeHTTP(rr, req)
if rr.Code != tt.wantStatus {
t.Fatalf("status = %d, want %d, body=%s", rr.Code, tt.wantStatus, rr.Body.String())
}
if tt.wantBody != "" && !strings.Contains(rr.Body.String(), tt.wantBody) {
t.Fatalf("body %q does not contain %q", rr.Body.String(), tt.wantBody)
}
})
}
}
एक केस को end-to-end वॉक करें: "missing password" के लिए आप केवल email वाली बॉडी भेजते हैं, Content-Type सेट करते हैं, इसे ServeHTTP से चलाते हैं, फिर 400 और एक एरर जिसे स्पष्ट रूप से password की ओर इशारा करता है, असर्ट करते हैं। उस एक केस से यह साबित होता है कि आपका डिकोडर, वैलिडेटर, और एरर रिस्पॉन्स फॉर्मैट एक साथ काम कर रहे हैं।
यदि आप कॉन्ट्रैक्ट्स, ऑथ मॉड्यूल्स, और इंटीग्रेशन को स्टैंडर्डाइज़ करने का तेज़ तरीका चाहते हैं जबकि फिर भी असली Go कोड शिप करते हैं, तो AppMaster (appmaster.io) इसके लिए बनाया गया है। तब भी, ये टेस्ट मूल्यवान रहते हैं क्योंकि वे उन बिहेवियर्स को लॉक कर देते हैं जिन पर आपके क्लाइंट्स निर्भर करते हैं।


