आधुनिक सॉफ्टवेयर विकास में, अनुप्रयोगों की विश्वसनीयता और स्थिरता सुनिश्चित करने में प्रभावी परीक्षण महत्वपूर्ण भूमिका निभाता है।
यह लेख एकीकरण परीक्षण लिखने के लिए व्यावहारिक सुझाव प्रदान करता है, यह दर्शाता है कि बाहरी सेवाओं के साथ बातचीत के विनिर्देशों पर कैसे ध्यान केंद्रित किया जाए, जिससे परीक्षण अधिक पठनीय और बनाए रखने में आसान हो। यह दृष्टिकोण न केवल परीक्षण की दक्षता को बढ़ाता है बल्कि एप्लिकेशन के भीतर एकीकरण प्रक्रियाओं की बेहतर समझ को भी बढ़ावा देता है। विशिष्ट उदाहरणों के लेंस के माध्यम से, विभिन्न रणनीतियों और उपकरणों - जैसे कि DSL रैपर, JsonAssert, और Pact - का पता लगाया जाएगा, जो पाठक को एकीकरण परीक्षणों की गुणवत्ता और दृश्यता में सुधार करने के लिए एक व्यापक मार्गदर्शिका प्रदान करेगा।
यह लेख स्प्रिंग अनुप्रयोगों में HTTP इंटरैक्शन का परीक्षण करने के लिए ग्रूवी में स्पॉक फ्रेमवर्क का उपयोग करके किए गए एकीकरण परीक्षणों के उदाहरण प्रस्तुत करता है। साथ ही, सुझाए गए मुख्य तकनीकों और दृष्टिकोणों को HTTP से परे विभिन्न प्रकार के इंटरैक्शन पर प्रभावी रूप से लागू किया जा सकता है।
स्प्रिंग में प्रभावी एकीकरण परीक्षण लिखना: HTTP अनुरोध मॉकिंग के लिए संगठित परीक्षण रणनीतियाँ लेख में अलग-अलग चरणों में स्पष्ट रूप से विभाजित परीक्षण लिखने के दृष्टिकोण का वर्णन किया गया है, जिनमें से प्रत्येक अपनी विशिष्ट भूमिका निभाता है। आइए इन अनुशंसाओं के अनुसार एक परीक्षण उदाहरण का वर्णन करें, लेकिन एक नहीं बल्कि दो अनुरोधों का मॉकिंग करें। संक्षिप्तता के लिए एक्ट चरण (निष्पादन) को छोड़ दिया जाएगा (एक पूर्ण परीक्षण उदाहरण प्रोजेक्ट रिपॉजिटरी में पाया जा सकता है)।
प्रस्तुत कोड सशर्त रूप से भागों में विभाजित है: "सहायक कोड" (ग्रे रंग में) और "बाहरी इंटरैक्शन का विनिर्देश" (नीले रंग में)। सहायक कोड में परीक्षण के लिए तंत्र और उपयोगिताएँ शामिल हैं, जिसमें अनुरोधों को रोकना और प्रतिक्रियाओं का अनुकरण करना शामिल है। बाहरी इंटरैक्शन का विनिर्देश बाहरी सेवाओं के बारे में विशिष्ट डेटा का वर्णन करता है, जिसके साथ सिस्टम को परीक्षण के दौरान बातचीत करनी चाहिए, जिसमें अपेक्षित अनुरोध और प्रतिक्रियाएँ शामिल हैं। सहायक कोड परीक्षण के लिए नींव रखता है, जबकि विनिर्देश सीधे उस सिस्टम के व्यावसायिक तर्क और मुख्य कार्यों से संबंधित होता है जिसका हम परीक्षण करने का प्रयास कर रहे हैं।
विनिर्देश कोड का एक छोटा सा हिस्सा घेरता है लेकिन परीक्षण को समझने के लिए महत्वपूर्ण मूल्य का प्रतिनिधित्व करता है, जबकि सहायक कोड, एक बड़े हिस्से पर कब्जा करता है, कम मूल्य प्रस्तुत करता है और प्रत्येक नकली घोषणा के लिए दोहरावदार होता है। कोड MockRestServiceServer के साथ उपयोग के लिए अभिप्रेत है। WireMock पर उदाहरण का संदर्भ देते हुए, कोई भी समान पैटर्न देख सकता है: विनिर्देश लगभग समान है, और सहायक कोड भिन्न है।
इस लेख का उद्देश्य परीक्षण लिखने के लिए व्यावहारिक सिफारिशें प्रस्तुत करना है, ताकि विनिर्देश पर ध्यान केन्द्रित किया जा सके, तथा सहायक कोड को पीछे रखा जा सके।
हमारे परीक्षण परिदृश्य के लिए, मैं एक काल्पनिक टेलीग्राम बॉट का प्रस्ताव करता हूं जो ओपनएआई एपीआई को अनुरोध भेजता है और उपयोगकर्ताओं को प्रतिक्रियाएं भेजता है।
सेवाओं के साथ बातचीत करने के लिए अनुबंधों को ऑपरेशन के मुख्य तर्क को उजागर करने के लिए सरलीकृत तरीके से वर्णित किया गया है। नीचे एप्लिकेशन आर्किटेक्चर को प्रदर्शित करने वाला एक अनुक्रम आरेख है। मैं समझता हूं कि डिज़ाइन सिस्टम आर्किटेक्चर के दृष्टिकोण से सवाल उठा सकता है, लेकिन कृपया इसे समझ के साथ देखें - यहाँ मुख्य लक्ष्य परीक्षणों में दृश्यता बढ़ाने के लिए एक दृष्टिकोण का प्रदर्शन करना है।
यह आलेख परीक्षण लिखने के लिए निम्नलिखित व्यावहारिक सिफारिशों पर चर्चा करता है:
DSL रैपर का उपयोग करने से बॉयलरप्लेट मॉक कोड को छिपाने की अनुमति मिलती है और विनिर्देश के साथ काम करने के लिए एक सरल इंटरफ़ेस प्रदान करता है। इस बात पर ज़ोर देना ज़रूरी है कि जो प्रस्तावित किया गया है वह कोई विशिष्ट DSL नहीं है बल्कि एक सामान्य दृष्टिकोण है जिसे यह लागू करता है। DSL का उपयोग करके एक सही परीक्षण उदाहरण नीचे प्रस्तुत किया गया है ( पूर्ण परीक्षण पाठ )।
setup: def openaiRequestCaptor = restExpectation.openai.completions(withSuccess("{...}")) def telegramRequestCaptor = restExpectation.telegram.sendMessage(withSuccess("{}")) when: ... then: openaiRequestCaptor.times == 1 telegramRequestCaptor.times == 1
जहाँ उदाहरण के लिए, विधि restExpectation.openai.completions
वर्णन इस प्रकार किया गया है:
public interface OpenaiMock { /** * This method configures the mock request to the following URL: {@code https://api.openai.com/v1/chat/completions} */ RequestCaptor completions(DefaultResponseCreator responseCreator); }
विधि पर टिप्पणी होने से, कोड संपादक में विधि नाम पर माउस घुमाते समय, सहायता प्राप्त करने की सुविधा मिलती है, जिसमें मॉक किए जाने वाले URL को देखना भी शामिल है।
प्रस्तावित कार्यान्वयन में, मॉक से प्रतिक्रिया की घोषणा ResponseCreator
इंस्टैंस का उपयोग करके की जाती है, जिससे कस्टम इंस्टैंस की अनुमति मिलती है, जैसे:
public static ResponseCreator withResourceAccessException() { return (request) -> { throw new ResourceAccessException("Error"); }; }
प्रतिक्रियाओं का एक सेट निर्दिष्ट करने वाले असफल परिदृश्यों के लिए एक उदाहरण परीक्षण नीचे दिखाया गया है:
import static org.springframework.http.HttpStatus.FORBIDDEN setup: def openaiRequestCaptor = restExpectation.openai.completions(openaiResponse) def telegramRequestCaptor = restExpectation.telegram.sendMessage(withSuccess("{}")) when: ... then: openaiRequestCaptor.times == 1 telegramRequestCaptor.times == 0 where: openaiResponse | _ withResourceAccessException() | _ withStatus(FORBIDDEN) | _
वायरमॉक के लिए, सब कुछ समान है, सिवाय इसके कि प्रतिक्रिया गठन थोड़ा अलग है ( परीक्षण कोड , प्रतिक्रिया फैक्ट्री क्लास कोड )।
DSL को लागू करते समय, IntelliJ IDEA में विशिष्ट कोड स्निपेट के लिए भाषा सुविधा समर्थन सक्षम करने के लिए @Language("JSON")
के साथ विधि पैरामीटर को एनोटेट करना संभव है। उदाहरण के लिए, JSON के साथ, संपादक स्ट्रिंग पैरामीटर को JSON कोड के रूप में मानेगा, जिससे सिंटैक्स हाइलाइटिंग, ऑटो-कम्प्लीशन, त्रुटि जाँच, नेविगेशन और संरचना खोज जैसी सुविधाएँ सक्षम होंगी। यहाँ एनोटेशन के उपयोग का एक उदाहरण दिया गया है:
public static DefaultResponseCreator withSuccess(@Language("JSON") String body) { return MockRestResponseCreators.withSuccess(body, APPLICATION_JSON); }
संपादक में यह इस प्रकार दिखता है:
JSONAssert लाइब्रेरी को JSON संरचनाओं के परीक्षण को सरल बनाने के लिए डिज़ाइन किया गया है। यह डेवलपर्स को उच्च स्तर की लचीलेपन के साथ अपेक्षित और वास्तविक JSON स्ट्रिंग की आसानी से तुलना करने में सक्षम बनाता है, विभिन्न तुलना मोड का समर्थन करता है।
यह इस तरह के सत्यापन विवरण से आगे बढ़ने की अनुमति देता है
openaiRequestCaptor.body.model == "gpt-3.5-turbo" openaiRequestCaptor.body.messages.size() == 1 openaiRequestCaptor.body.messages[0].role == "user" openaiRequestCaptor.body.messages[0].content == "Hello!"
कुछ इस तरह
assertEquals("""{ "model": "gpt-3.5-turbo", "messages": [{ "role": "user", "content": "Hello!" }] }""", openaiRequestCaptor.bodyString, false)
मेरी राय में, दूसरे दृष्टिकोण का मुख्य लाभ यह है कि यह विभिन्न संदर्भों में डेटा प्रतिनिधित्व की स्थिरता सुनिश्चित करता है - दस्तावेज़ीकरण, लॉग और परीक्षणों में। यह परीक्षण प्रक्रिया को काफी सरल बनाता है, तुलना में लचीलापन और त्रुटि निदान में सटीकता प्रदान करता है। इस प्रकार, हम न केवल परीक्षण लिखने और बनाए रखने में समय बचाते हैं बल्कि उनकी पठनीयता और सूचनात्मकता में भी सुधार करते हैं।
स्प्रिंग बूट के भीतर काम करते समय, कम से कम संस्करण 2 से शुरू करते हुए, लाइब्रेरी के साथ काम करने के लिए किसी अतिरिक्त निर्भरता की आवश्यकता नहीं होती है, क्योंकि org.springframework.boot:spring-boot-starter-test
पहले से ही org.skyscreamer:jsonassert
पर निर्भरता शामिल है।
एक अवलोकन जो हम कर सकते हैं वह यह है कि JSON स्ट्रिंग्स परीक्षण का एक महत्वपूर्ण हिस्सा लेती हैं। क्या उन्हें छिपाया जाना चाहिए? हाँ और नहीं। यह समझना महत्वपूर्ण है कि क्या अधिक लाभ लाता है। उन्हें छिपाने से परीक्षण अधिक कॉम्पैक्ट हो जाते हैं और पहली नज़र में परीक्षण के सार को समझना आसान हो जाता है। दूसरी ओर, गहन विश्लेषण के लिए, बाहरी इंटरैक्शन के विनिर्देश के बारे में महत्वपूर्ण जानकारी का हिस्सा छिपाया जाएगा, जिसके लिए फ़ाइलों में अतिरिक्त जंप की आवश्यकता होगी। निर्णय सुविधा पर निर्भर करता है: वही करें जो आपके लिए अधिक सुविधाजनक हो।
यदि आप JSON स्ट्रिंग को फ़ाइलों में संग्रहीत करना चुनते हैं, तो एक सरल विकल्प यह है कि आप प्रतिक्रियाओं और अनुरोधों को JSON फ़ाइलों में अलग-अलग रखें। नीचे एक परीक्षण कोड ( पूर्ण संस्करण ) है जो कार्यान्वयन विकल्प को प्रदर्शित करता है:
setup: def openaiRequestCaptor = restExpectation.openai.completions(withSuccess(fromFile("json/openai/response.json"))) def telegramRequestCaptor = restExpectation.telegram.sendMessage(withSuccess("{}")) when: ... then: openaiRequestCaptor.times == 1 telegramRequestCaptor.times == 1
fromFile
विधि केवल src/test/resources
निर्देशिका में एक फ़ाइल से एक स्ट्रिंग को पढ़ती है और इसमें कोई क्रांतिकारी विचार नहीं है, लेकिन यह अभी भी संदर्भ के लिए परियोजना रिपोजिटरी में उपलब्ध है।
स्ट्रिंग के परिवर्तनशील भाग के लिए, org.apache.commons.text.StringSubstitutor के साथ प्रतिस्थापन का उपयोग करने और मॉक का वर्णन करते समय मानों का एक सेट पास करने का सुझाव दिया जाता है, उदाहरण के लिए:
setup: def openaiRequestCaptor = restExpectation.openai.completions(withSuccess(fromFile("json/openai/response.json", [content: "Hello! How can I assist you today?"])))
जहाँ JSON फ़ाइल में प्रतिस्थापन वाला भाग इस प्रकार दिखता है:
... "message": { "role": "assistant", "content": "${content:-Hello there, how may I assist you today?}" }, ...
फ़ाइल संग्रहण दृष्टिकोण को अपनाते समय डेवलपर्स के लिए एकमात्र चुनौती परीक्षण संसाधनों में एक उचित फ़ाइल प्लेसमेंट योजना और एक नामकरण योजना विकसित करना है। ऐसी गलती करना आसान है जो इन फ़ाइलों के साथ काम करने के अनुभव को खराब कर सकती है। इस समस्या का एक समाधान विनिर्देशों का उपयोग करना हो सकता है, जैसे कि Pact से, जिस पर बाद में चर्चा की जाएगी।
ग्रूवी में लिखे गए परीक्षणों में वर्णित दृष्टिकोण का उपयोग करते समय, आपको असुविधा का सामना करना पड़ सकता है: कोड से फ़ाइल तक नेविगेट करने के लिए IntelliJ IDEA में कोई समर्थन नहीं है, लेकिन भविष्य में इस कार्यक्षमता के लिए समर्थन जोड़े जाने की उम्मीद है । जावा में लिखे गए परीक्षणों में, यह बहुत अच्छा काम करता है।
आइये शब्दावली से शुरू करें।
अनुबंध परीक्षण एकीकरण बिंदुओं के परीक्षण की एक विधि है, जहाँ प्रत्येक एप्लिकेशन को अलग-अलग परीक्षण करके यह पुष्टि की जाती है कि उसके द्वारा भेजे या प्राप्त किए गए संदेश "अनुबंध" में दर्ज आपसी समझ के अनुरूप हैं। यह दृष्टिकोण सुनिश्चित करता है कि सिस्टम के विभिन्न भागों के बीच की बातचीत अपेक्षाओं को पूरा करती है।
अनुबंध परीक्षण के संदर्भ में अनुबंध एक दस्तावेज या विनिर्देश है जो अनुप्रयोगों के बीच आदान-प्रदान किए जाने वाले संदेशों (अनुरोधों और प्रतिक्रियाओं) के प्रारूप और संरचना पर एक समझौते को रिकॉर्ड करता है। यह इस बात की पुष्टि करने के लिए एक आधार के रूप में कार्य करता है कि प्रत्येक अनुप्रयोग एकीकरण में दूसरों द्वारा भेजे और प्राप्त किए गए डेटा को सही ढंग से संसाधित कर सकता है।
यह अनुबंध एक उपभोक्ता (उदाहरण के लिए, एक क्लाइंट जो कुछ डेटा प्राप्त करना चाहता है) और एक प्रदाता (उदाहरण के लिए, क्लाइंट द्वारा आवश्यक डेटा प्रदान करने वाला सर्वर पर एक API) के बीच स्थापित होता है।
उपभोक्ता-संचालित परीक्षण अनुबंध परीक्षण का एक दृष्टिकोण है जहाँ उपभोक्ता अपने स्वचालित परीक्षण रन के दौरान अनुबंध उत्पन्न करते हैं। ये अनुबंध प्रदाता को भेजे जाते हैं, जो फिर अपने स्वचालित परीक्षणों का सेट चलाता है। अनुबंध फ़ाइल में निहित प्रत्येक अनुरोध प्रदाता को भेजा जाता है, और प्राप्त प्रतिक्रिया की तुलना अनुबंध फ़ाइल में निर्दिष्ट अपेक्षित प्रतिक्रिया से की जाती है। यदि दोनों प्रतिक्रियाएँ मेल खाती हैं, तो इसका मतलब है कि उपभोक्ता और सेवा प्रदाता संगत हैं।
अंत में, पैक्ट। पैक्ट एक ऐसा उपकरण है जो उपभोक्ता-संचालित अनुबंध परीक्षण के विचारों को लागू करता है। यह HTTP एकीकरण और संदेश-आधारित एकीकरण दोनों के परीक्षण का समर्थन करता है, जो कोड-प्रथम परीक्षण विकास पर ध्यान केंद्रित करता है।
जैसा कि मैंने पहले बताया, हम अपने कार्य के लिए Pact के अनुबंध विनिर्देशों और उपकरणों का उपयोग कर सकते हैं। कार्यान्वयन इस तरह दिख सकता है ( पूर्ण परीक्षण कोड ):
setup: def openaiRequestCaptor = restExpectation.openai.completions(fromContract("openai/SuccessfulCompletion-Hello.json")) def telegramRequestCaptor = restExpectation.telegram.sendMessage(withSuccess("{}")) when: ... then: openaiRequestCaptor.times == 1 telegramRequestCaptor.times == 1
अनुबंध फ़ाइल समीक्षा के लिए उपलब्ध है.
अनुबंध फ़ाइलों का उपयोग करने का लाभ यह है कि उनमें न केवल अनुरोध और प्रतिक्रिया निकाय शामिल होते हैं, बल्कि बाहरी इंटरैक्शन विनिर्देश के अन्य तत्व भी होते हैं - अनुरोध पथ, हेडर और HTTP प्रतिक्रिया स्थिति, जिससे इस तरह के अनुबंध के आधार पर मॉक का पूरी तरह से वर्णन किया जा सकता है।
यह ध्यान रखना महत्वपूर्ण है कि इस मामले में हम खुद को अनुबंध परीक्षण तक सीमित रखते हैं और उपभोक्ता-संचालित परीक्षण तक नहीं बढ़ाते हैं। हालाँकि, कोई व्यक्ति Pact के बारे में और अधिक जानना चाह सकता है।
इस लेख में स्प्रिंग फ्रेमवर्क के साथ विकास के संदर्भ में एकीकरण परीक्षणों की दृश्यता और दक्षता बढ़ाने के लिए व्यावहारिक अनुशंसाओं की समीक्षा की गई है। मेरा लक्ष्य बाहरी इंटरैक्शन के विनिर्देशों को स्पष्ट रूप से परिभाषित करने और बॉयलरप्लेट कोड को कम करने के महत्व पर ध्यान केंद्रित करना था। इस लक्ष्य को प्राप्त करने के लिए, मैंने DSL रैपर और JsonAssert का उपयोग करने, JSON फ़ाइलों में विनिर्देशों को संग्रहीत करने और Pact के माध्यम से अनुबंधों के साथ काम करने का सुझाव दिया। लेख में वर्णित दृष्टिकोणों का उद्देश्य परीक्षण लिखने और बनाए रखने की प्रक्रिया को सरल बनाना, उनकी पठनीयता में सुधार करना और सबसे महत्वपूर्ण बात, सिस्टम घटकों के बीच इंटरैक्शन को सटीक रूप से दर्शाकर परीक्षण की गुणवत्ता को बढ़ाना है।
परीक्षणों को प्रदर्शित करने वाले प्रोजेक्ट रिपोजिटरी का लिंक - सैंडबॉक्स/बॉट .
लेख पर ध्यान देने के लिए आपका धन्यवाद, तथा प्रभावी और दृश्यमान परीक्षण लिखने के आपके प्रयास में आपको शुभकामनाएँ!