`test::nginx` के परीक्षण विधियाँ: कॉन्फ़िगरेशन, अनुरोध भेजना, और प्रतिक्रियाओं को संभालना

API7.ai

November 18, 2022

OpenResty (NGINX + Lua)

पिछले लेख तक, हमने test::nginx का पहला झलक देख लिया है और सबसे सरल उदाहरण चला लिया है। हालांकि, एक वास्तविक ओपन-सोर्स प्रोजेक्ट में, test::nginx में लिखे गए टेस्ट केस सैंपल कोड की तुलना में कहीं अधिक जटिल और महारत हासिल करने में कठिन होते हैं। अन्यथा, इसे रोडब्लॉक नहीं कहा जाता।

इस लेख में, मैं आपको test::nginx में अक्सर उपयोग किए जाने वाले कमांड्स और टेस्ट विधियों के माध्यम से ले जाऊंगा, ताकि आप OpenResty प्रोजेक्ट में अधिकांश टेस्ट केस सेट को समझ सकें और अधिक वास्तविक टेस्ट केस लिखने की क्षमता प्राप्त कर सकें। भले ही आपने अभी तक OpenResty में कोड योगदान नहीं दिया हो, OpenResty के टेस्टिंग फ्रेमवर्क से परिचित होना आपके काम में टेस्ट केस डिजाइन और लिखने के लिए एक बड़ी प्रेरणा होगा।

test::nginx टेस्ट मूल रूप से प्रत्येक टेस्ट केस के कॉन्फ़िगरेशन के आधार पर nginx.conf जनरेट करता है और एक NGINX प्रक्रिया शुरू करता है। फिर, यह निर्दिष्ट अनुरोध बॉडी और हेडर्स के साथ एक क्लाइंट अनुरोध का अनुकरण करता है। इसके बाद, टेस्ट केस में Lua कोड अनुरोध को प्रोसेस करता है और एक प्रतिक्रिया बनाता है। इस समय, test::nginx प्रतिक्रिया बॉडी, प्रतिक्रिया हेडर्स, और त्रुटि लॉग जैसी महत्वपूर्ण जानकारी को पार्स करता है और उनकी तुलना टेस्ट कॉन्फ़िगरेशन से करता है। यदि कोई अंतर होता है, तो टेस्ट विफल हो जाता है और एक त्रुटि दिखाई देती है; अन्यथा, यह सफल होता है।

test::nginx बहुत सारे DSL (डोमेन-विशिष्ट भाषा) प्रिमिटिव प्रदान करता है। मैंने NGINX को कॉन्फ़िगर करने, अनुरोध भेजने, प्रतिक्रिया प्रोसेस करने, और लॉग की जांच करने के अनुसार एक सरल वर्गीकरण किया है। यह 20% कार्यक्षमता 80% एप्लिकेशन परिदृश्यों को कवर कर सकती है, इसलिए हमें इस पर मजबूत पकड़ बनानी चाहिए। अन्य अधिक उन्नत प्रिमिटिव और उपयोग के बारे में, हम अगले लेख में चर्चा करेंगे।

NGINX कॉन्फ़िगरेशन

पहले NGINX कॉन्फ़िगरेशन को देखते हैं। test::nginx का "config" कीवर्ड वाला प्रिमिटिव NGINX कॉन्फ़िगरेशन से संबंधित है, जैसे config, stream_config, http_config, आदि।

इनके कार्य समान हैं: निर्दिष्ट NGINX कॉन्फ़िगरेशन को विभिन्न NGINX संदर्भों में डालना। ये कॉन्फ़िगरेशन या तो NGINX कमांड हो सकते हैं या content_by_lua_block में संलग्न Lua कोड।

यूनिट टेस्ट करते समय, config सबसे अधिक उपयोग किया जाने वाला प्रिमिटिव है जिसमें हम Lua लाइब्रेरीज़ लोड करते हैं और व्हाइट-बॉक्स टेस्टिंग के लिए फ़ंक्शन कॉल करते हैं। यहां एक टेस्ट कोड स्निपेट है, जो पूरी तरह से नहीं चल सकता है। यह एक वास्तविक ओपन-सोर्स प्रोजेक्ट से है, इसलिए यदि आप रुचि रखते हैं, तो आप पूर्ण टेस्ट देखने के लिए लिंक पर क्लिक कर सकते हैं, या आप इसे स्थानीय रूप से चलाने का प्रयास कर सकते हैं।

=== TEST 1: sanity --- config location /t { content_by_lua_block { local plugin = require("apisix.plugins.key-auth") local ok, err = plugin.check_schema({key = 'test-key'}) if not ok then ngx.say(err) end ngx.say("done") } }

इस टेस्ट केस का उद्देश्य plugins.key-auth कोड फ़ाइल में check_schema फ़ंक्शन की जांच करना है कि यह सही ढंग से काम करता है या नहीं। यह location /t में content_by_lua_block NGINX कमांड का उपयोग करता है ताकि टेस्ट किए जाने वाले मॉड्यूल को रिक्वायर किया जा सके और सीधे जांचे जाने वाले फ़ंक्शन को कॉल किया जा सके।

यह test::nginx में व्हाइट-बॉक्स टेस्टिंग का एक सामान्य तरीका है। हालांकि, यह कॉन्फ़िगरेशन अकेले टेस्ट को पूरा करने के लिए पर्याप्त नहीं है, इसलिए आगे बढ़ते हैं और देखते हैं कि क्लाइंट अनुरोध कैसे भेजा जाए।

अनुरोध भेजना

क्लाइंट द्वारा अनुरोध भेजने का अनुकरण करने में काफी विवरण शामिल होते हैं, इसलिए सबसे सरल से शुरू करते हैं - एकल अनुरोध भेजना।

request

उपरोक्त टेस्ट केस को जारी रखते हुए, यदि हम चाहते हैं कि यूनिट टेस्ट कोड चले, तो हमें कॉन्फ़िग में निर्दिष्ट /t पते पर एक HTTP अनुरोध शुरू करना होगा, जैसा कि निम्नलिखित टेस्ट कोड में दिखाया गया है:

--- request GET /t

यह कोड अनुरोध प्रिमिटिव में /t पर एक GET अनुरोध भेजता है। यहां, हम एक्सेस के IP पते, डोमेन नाम, या पोर्ट को निर्दिष्ट नहीं करते हैं, न ही यह निर्दिष्ट करते हैं कि यह HTTP 1.0 या HTTP 1.1 है। ये सभी विवरण test::nginx द्वारा छिपाए जाते हैं, इसलिए हमें इनकी चिंता करने की आवश्यकता नहीं है। यह DSL का एक लाभ है - हमें केवल व्यावसायिक तर्क पर ध्यान केंद्रित करने की आवश्यकता है, सभी विवरणों से विचलित होने की नहीं।

इसके अलावा, यह आंशिक लचीलापन प्रदान करता है। उदाहरण के लिए, डिफ़ॉल्ट रूप से HTTP 1.1 के लिए प्रोटोकॉल है, या यदि हम HTTP 1.0 का परीक्षण करना चाहते हैं, तो हम अलग से निर्दिष्ट कर सकते हैं:

--- request GET /t HTTP/1.0

GET विधि के अलावा, POST विधि का भी समर्थन करने की आवश्यकता है। निम्नलिखित उदाहरण में, हम स्ट्रिंग hello world को निर्दिष्ट पते पर POST कर सकते हैं।

--- request POST /t hello world

फिर से, test::nginx यहां आपके लिए अनुरोध बॉडी की लंबाई की गणना करता है, और host और connection अनुरोध हेडर्स को स्वचालित रूप से जोड़ता है ताकि यह सुनिश्चित हो सके कि यह एक सामान्य अनुरोध है।

बेशक, हम इसे और अधिक पठनीय बनाने के लिए टिप्पणियाँ जोड़ सकते हैं। # से शुरू होने वाली टिप्पणियाँ कोड टिप्पणियों के रूप में पहचानी जाएंगी।

--- request # post request POST /t hello world

अनुरोध एक अधिक जटिल और लचीली मोड का भी समर्थन करता है, जो eval को एक फ़िल्टर के रूप में उपयोग करता है ताकि Perl कोड को सीधे एम्बेड किया जा सके क्योंकि test::nginx Perl में लिखा गया है। यदि वर्तमान DSL भाषा आपकी आवश्यकताओं को पूरा नहीं करती है, तो eval Perl कोड को सीधे निष्पादित करने का "अंतिम हथियार" है।

eval के उपयोग के लिए, यहां कुछ सरल उदाहरण देखते हैं, और हम अगले लेख में अन्य अधिक जटिल उदाहरणों के साथ जारी रखेंगे।

--- request eval "POST /t hello\x00\x01\x02 world\x03\x04\xff"

पहले उदाहरण में, हम eval का उपयोग करके गैर-प्रिंटेबल वर्णों को निर्दिष्ट करते हैं, जो इसके उपयोगों में से एक है। डबल कोट्स के बीच की सामग्री को Perl स्ट्रिंग के रूप में माना जाएगा और फिर request को एक तर्क के रूप में पास किया जाएगा।

यहां एक और दिलचस्प उदाहरण है:

--- request eval "POST /t\n" . "a" x 1024

हालांकि, इस उदाहरण को समझने के लिए, हमें Perl में स्ट्रिंग्स के बारे में कुछ जानना होगा, इसलिए मुझे यहां दो बिंदुओं का संक्षेप में उल्लेख करना होगा।

  • Perl में, हम स्ट्रिंग संयोजन के लिए एक डॉट का उपयोग करते हैं। क्या यह Lua के दो डॉट्स से कुछ हद तक समान नहीं है?
  • एक लोअरकेस x एक वर्ण को दोहराने की संख्या को दर्शाता है। उदाहरण के लिए, उपरोक्त "a" x 1024 का अर्थ है कि वर्ण "a" को 1024 बार दोहराया जाएगा।

तो, दूसरे उदाहरण का अर्थ है कि POST विधि /t पते पर 1024 वर्ण a वाले अनुरोध को भेजता है।

pipelined_requests

एकल अनुरोध भेजने का तरीका समझने के बाद, आइए देखें कि कैसे कई अनुरोध भेजे जाएं। test::nginx में, हम pipelined_requests प्रिमिटिव का उपयोग करके एक ही keep-alive कनेक्शन के भीतर कई अनुरोधों को क्रमिक रूप से भेज सकते हैं:

--- pipelined_requests eval ["GET /hello", "GET /world", "GET /foo", "GET /bar"]

उदाहरण के लिए, यह उदाहरण एक ही कनेक्शन में इन चार APIs को क्रमिक रूप से एक्सेस करेगा। इसके दो लाभ हैं:

  • पहला यह है कि बहुत सारे दोहराव वाले टेस्ट कोड को समाप्त किया जा सकता है, और चार टेस्ट केस को एक में संपीड़ित किया जा सकता है।
  • दूसरा और सबसे महत्वपूर्ण कारण यह है कि हम पाइपलाइन्ड अनुरोधों का उपयोग करके यह पता लगा सकते हैं कि क्या कोड लॉजिक कई एक्सेस के मामले में अपवाद होगा।

आप सोच रहे होंगे, मैं कई टेस्ट केस को क्रमिक रूप से लिखता हूं, तो कोड भी निष्पादन चरण में कई बार निष्पादित होगा। क्या यह उपरोक्त दूसरी समस्या को भी कवर नहीं करता है?

यह test::nginx के निष्पादन मोड पर निर्भर करता है, जो आपके विचार से अलग तरीके से काम करता है। प्रत्येक टेस्ट केस के बाद, test::nginx वर्तमान NGINX प्रक्रिया को बंद कर देता है, और मेमोरी में सभी डेटा गायब हो जाता है। अगले टेस्ट केस को चलाते समय, nginx.conf को फिर से जनरेट किया जाता है, और एक नया NGINX Worker शुरू किया जाता है। यह तंत्र सुनिश्चित करता है कि टेस्ट केस एक दूसरे को प्रभावित नहीं करते हैं।

इसलिए, जब हम कई अनुरोधों का परीक्षण करना चाहते हैं, तो हमें pipelined_requests प्रिमिटिव का उपयोग करने की आवश्यकता होती है। इसके आधार पर, हम रेट-लिमिटिंग, कंकरेंसी-लिमिटिंग, और कई अन्य परिदृश्यों का अनुकरण कर सकते हैं ताकि यह परीक्षण किया जा सके कि आपका सिस्टम अधिक वास्तविक और जटिल परिदृश्यों में सही ढंग से काम करता है। हम इसे अगले लेख के लिए भी छोड़ देंगे, क्योंकि इसमें कई कमांड्स और प्रिमिटिव्स शामिल होंगे।

repeat_each

हमने अभी कई अनुरोधों का परीक्षण करने के मामले का उल्लेख किया है, तो एक ही टेस्ट को कई बार निष्पादित करने के लिए हमें क्या करना चाहिए?

इस समस्या के लिए, test::nginx एक वैश्विक सेटिंग प्रदान करता है: repeat_each, जो एक Perl फ़ंक्शन है जो डिफ़ॉल्ट रूप से repeat_each(1) होता है, जो इंगित करता है कि टेस्ट केस केवल एक बार चलेगा। इसलिए पिछले टेस्ट केस में, हम इसे अलग से सेट करने की परेशानी नहीं उठाते हैं।

स्वाभाविक रूप से, हम इसे run_test() फ़ंक्शन से पहले सेट कर सकते हैं, उदाहरण के लिए, तर्क को 2 में बदलकर।

repeat_each(2); run_tests();

फिर, प्रत्येक टेस्ट केस दो बार चलेगा, और इसी तरह।

more_headers

अनुरोध बॉडी के बारे में बात करने के बाद, आइए अनुरोध हेडर्स को देखें। जैसा कि हमने ऊपर उल्लेख किया है, test::nginx डिफ़ॉल्ट रूप से host और connection हेडर्स के साथ अनुरोध भेजता है। अन्य अनुरोध हेडर्स के बारे में क्या?

more_headers विशेष रूप से इसके लिए बनाया गया है।

--- more_headers X-Foo: blah

हम इसका उपयोग विभिन्न कस्टम हेडर्स सेट करने के लिए कर सकते हैं। यदि हम एक से अधिक हेडर सेट करना चाहते हैं, तो एक से अधिक लाइन सेट करें:

--- more_headers X-Foo: 3 User-Agent: openresty

प्रतिक्रिया प्रोसेस करना

अनुरोध भेजने के बाद, test::nginx का सबसे महत्वपूर्ण हिस्सा प्रतिक्रिया को प्रोसेस करना है, जहां हम यह निर्धारित करेंगे कि प्रतिक्रिया अपेक्षाओं को पूरा करती है या नहीं। यहां हम इसे चार भागों में विभाजित करते हैं और उनका परिचय देते हैं: प्रतिक्रिया बॉडी, प्रतिक्रिया हेडर, प्रतिक्रिया स्थिति कोड, और लॉग।

response_body

अनुरोध प्रिमिटिव का प्रतिरूप response_body है, और निम्नलिखित उनके दो कॉन्फ़िगरेशन के उपयोग का एक उदाहरण है:

=== TEST 1: sanity --- config location /t { content_by_lua_block { ngx.say("hello") } } --- request GET /t --- response_body hello

यह टेस्ट केस पास होगा यदि प्रतिक्रिया बॉडी hello है, और अन्य मामलों में त्रुटि रिपोर्ट करेगा। लेकिन हम एक लंबी वापसी बॉडी के लिए कैसे परीक्षण करें? चिंता न करें, test::nginx ने पहले ही आपके लिए इसका ध्यान रखा है। यह नियमित अभिव्यक्ति के साथ प्रतिक्रिया बॉडी का पता लगाने का समर्थन करता है, जैसे निम्नलिखित:

--- response_body_like ^he\w+$

यह आपको प्रतिक्रिया बॉडी के साथ बहुत लचीला बनाता है। इसके अलावा, test::nginx unlike ऑपरेशन का भी समर्थन करता है:

--- response_body_unlike ^he\w+$

इस बिंदु पर, यदि प्रतिक्रिया बॉडी hello है, तो टेस्ट पास नहीं होगा।

इसी तरह, एकल अनुरोध का पता लगाने को समझने के बाद, आइए कई अनुरोधों का पता लगाने को देखें। यहां pipelined_requests के साथ इसका उपयोग करने का एक उदाहरण है:

--- pipelined_requests eval ["GET /hello", "GET /world", "GET /foo", "GET /bar"] --- response_body eval ["hello", "world", "oo", "bar"]

बेशक, यहां ध्यान देने योग्य महत्वपूर्ण बात यह है कि जितने अनुरोध आप भेजते हैं, उतने ही प्रतिक्रियाओं की आवश्यकता होती है।

response_headers

दूसरा, प्रतिक्रिया हेडर के बारे में बात करते हैं। प्रतिक्रिया हेडर अनुरोध हेडर के समान है जहां प्रत्येक लाइन एक हेडर की कुंजी और मान से मेल खाती है।

--- response_headers X-RateLimit-Limit: 2 X-RateLimit-Remaining: 1

प्रतिक्रिया बॉडी के पता लगाने की तरह, प्रतिक्रिया हेडर भी नियमित अभिव्यक्ति और unlike ऑपरेशन का समर्थन करते हैं, जैसे response_headers_like, raw_response_headers_like, और raw_response_headers_unlike

error_code

तीसरा प्रतिक्रिया कोड है। प्रतिक्रिया कोड का पता लगाना सीधे तुलना का समर्थन करता है और like ऑपरेशन का भी समर्थन करता है, जैसे निम्नलिखित दो उदाहरण:

--- error_code: 302
--- error_code_like: ^(?:500)?$

कई अनुरोधों के मामले में, error_code को कई बार जांचने की आवश्यकता होती है:

--- pipelined_requests eval ["GET /hello", "GET /hello", "GET /hello", "GET /hello"] --- error_code eval [200, 200, 503, 503]

error_log

अंतिम टेस्ट आइटम त्रुटि लॉग है। अधिकांश टेस्ट केस में, कोई त्रुटि लॉग उत्पन्न नहीं होता है। हम no_error_log का उपयोग करके पता लगा सकते हैं:

--- no_error_log [error]

उपरोक्त उदाहरण में, यदि NGINX error.log में स्ट्रिंग [error] दिखाई देती है, तो टेस्ट विफल हो जाएगा। यह एक बहुत ही सामान्य सुविधा है, और यह अनुशंसा की जाती है कि आप अपने सभी सामान्य टेस्ट में त्रुटि लॉग का पता लगाने को जोड़ें।

--- error_log hello world

उपरोक्त कॉन्फ़िगरेशन error.log में hello world की उपस्थिति का पता लगा रहा है। बेशक, आप Perl कोड में एम्बेडेड eval का उपयोग करके नियमित अभिव्यक्ति का पता लगाने को लागू कर सकते हैं, जैसे निम्नलिखित:

--- error_log eval qr/\[notice\] .*? \d+ hello world/

सारांश

आज, हम test::nginx में अनुरोध भेजने और प्रतिक्रिया का परीक्षण करने का तरीका सीख रहे हैं, जिसमें अनुरोध बॉडी, हेडर, प्रतिक्रिया स्थिति कोड, और त्रुटि लॉग शामिल हैं। हम इन प्रिमिटिव्स के संयोजन के साथ टेस्ट केस का एक पूरा सेट लागू कर सकते हैं।

अंत में, यहां एक सोचने वाला प्रश्न है: test::nginx, एक अमूर्त DSL के क्या फायदे और नुकसान हैं? मुझे टिप्पणियाँ छोड़ने और मेरे साथ चर्चा करने के लिए स्वतंत्र महसूस करें, और आप इस लेख को साझा करके एक साथ संवाद और सोचने के लिए भी आमंत्रित हैं।