TDD के नजरिए से, क्या मैं एक बुरा व्यक्ति हूं अगर मैं एक नकली के बजाय एक लाइव समापन बिंदु के खिलाफ परीक्षण करता हूं?


16

मैं धार्मिक रूप से टीडीडी का पालन करता हूं। मेरी परियोजनाओं में आम तौर पर सार्थक परीक्षण मामलों के साथ 85% या बेहतर परीक्षण कवरेज है।

मैं HBase के साथ बहुत काम करता हूं , और मुख्य ग्राहक इंटरफ़ेस, HTable, नकली करने के लिए एक वास्तविक दर्द है। मेरी इकाई परीक्षणों को लिखने के लिए मुझे 3 या 4 बार अधिक समय लगता है, यह उन परीक्षणों को लिखने के लिए करता है जो एक लाइव समापन बिंदु का उपयोग करते हैं।

मुझे पता है कि, दार्शनिक रूप से, मॉक का उपयोग करने वाले परीक्षणों को उन परीक्षणों पर प्राथमिकता देनी चाहिए जो एक लाइव एंडपॉइंट का उपयोग करते हैं। लेकिन HTable का मजाक उड़ाना एक गंभीर दर्द है, और मुझे यकीन नहीं है कि यह लाइव HBase उदाहरण के खिलाफ परीक्षण पर एक लाभ प्रदान करता है।

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

अभी, मैं अपने सभी वर्गों के लिए लाइव एंडपॉइंट परीक्षण और मॉक-आधारित परीक्षण लिखता हूं। मैं मोज़े को खोदना पसंद करूंगा, लेकिन मैं गुणवत्ता को परिणाम के रूप में अस्वीकार नहीं करना चाहता।

आप सब क्या सोचते हैं?


8
लाइव समापन बिंदु वास्तव में एक इकाई परीक्षण नहीं है, क्या यह है? यह एक एकीकरण परीक्षण है। लेकिन अंततः यह व्यावहारिकता का सवाल है; आप या तो समय लिखने का समय बिता सकते हैं, या समय लेखन सुविधाओं या बग को ठीक करने में खर्च कर सकते हैं।
रॉबर्ट हार्वे

4
मैंने अपने स्वयं के कोड के खिलाफ एक यूनिट टेस्ट चलाकर लोगों को 3rd पार्टी सेवाओं को लेने की कहानियां सुनी हैं ... जो कि एक लाइव एंडपॉइंट के लिए झुका हुआ था। दर सीमित नहीं है कि इकाई परीक्षण आमतौर पर करता है या परवाह करता है।

14
तुम बुरे व्यक्ति नहीं हो। तुम बुरे काम करने वाले अच्छे इंसान हो।
किरालासे

15
मैं धार्मिक रूप से TDD का पालन करता हूं। शायद यह समस्या है? मुझे नहीं लगता कि इस पद्धति का कोई मतलब है कि इसे गंभीरता से लिया जाए । ;)
FrustratedWithFormsDesigner

9
TDD का धार्मिक रूप से अनुसरण करने का मतलब होगा कि आप 15% अनलॉक्ड कोड को छोड़ देंगे।
मौविसील

जवाबों:


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

  • कहा जा रहा है, मान लें कि हम उन सभी प्रकारों के बारे में बात कर रहे हैं, जो आप सभी के हैं। यदि आपके मॉक-आधारित परीक्षण खुश पथ परिदृश्यों पर केंद्रित हैं, जहां सब कुछ सुचारू रूप से चला जाता है, तो आप उन्हें खोदते हुए कुछ भी नहीं खोएंगे क्योंकि आपके एकीकरण परीक्षण शायद पहले से ही सटीक पथों का परीक्षण कर रहे हैं।

    हालाँकि, पृथक परीक्षण तब दिलचस्प हो जाते हैं जब आप यह सोचना शुरू कर देते हैं कि परीक्षण के तहत आपका सिस्टम हर उस छोटी चीज पर कैसे प्रतिक्रिया दे सकता है जो उसके सहयोगी के अनुबंध में परिभाषित हो सकती है, चाहे वह वास्तविक ठोस वस्तु की बात कर रहा हो। यह कुछ सही बुनियादी कॉल का हिस्सा है । उन छोटे मामलों में से कई और उनमें से कई और संयोजन हो सकते हैं। यह वह जगह है जहां एकीकरण परीक्षण घटिया होने लगते हैं जबकि पृथक परीक्षण तेज और प्रबंधनीय रहेंगे।

    अधिक ठोस होने के लिए, यदि आपके HTable एडाप्टर के तरीकों में से एक खाली सूची देता है तो क्या होगा? यदि यह अशक्त हो तो क्या होगा? क्या होगा अगर यह एक कनेक्शन अपवाद फेंकता है? एडॉप्टर के अनुबंध में इसे परिभाषित किया जाना चाहिए अगर उन चीजों में से कोई भी हो सकता है, और इसके किसी भी उपभोक्ता को इन स्थितियों से निपटने के लिए तैयार रहना चाहिए , इसलिए उनके लिए परीक्षणों की आवश्यकता है।

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


+1 मुझे उन मामलों के साथ डेटाबेस को पॉप करने की तुलना में मॉक टेस्ट के साथ किनारे मामलों का निर्माण करना बहुत आसान लगता है।
रोब

मैं आपके अधिकांश उत्तर से सहमत हूं। हालांकि, मुझे यकीन नहीं है कि मैं एडेप्टर भाग के बारे में सहमत हूं। HTable का मजाक उड़ाने के लिए एक दर्द है क्योंकि यह काफी नंगे धातु है। उदाहरण के लिए, यदि आप एक बैच ऑपरेशन करना चाहते हैं, तो आपको गेट ऑब्जेक्ट्स का एक गुच्छा बनाना होगा, उन्हें एक सूची में डालना होगा, फिर HTable.batch () कॉल करें। मजाक के नजरिए से, यह एक गंभीर दर्द है, क्योंकि आपको एक कस्टम मिलानकर्ता बनाना होगा जो आपके द्वारा HTable.batch () में पास होने वाली वस्तुओं की सूची की जांच करता है और फिर प्राप्त करने की सूची () वस्तुओं के लिए सही परिणाम देता है। एक गंभीर दर्द।
संगफ्राइड

मुझे लगता है कि मैं HTable के लिए एक अच्छा, अनुकूल रैपर क्लास बना सकता हूं जिसने उस हाउसकीपिंग का ध्यान रखा, लेकिन उस समय ... मुझे ऐसा लगता है कि मैं HTable के चारों ओर एक फ्रेमवर्क का निर्माण कर रहा हूं, और क्या मुझे वास्तव में अपना काम करना चाहिए? आमतौर पर "चलो एक ढांचा बनाते हैं!" एक संकेत है मैं गलत दिशा में जा रहा हूँ। मैं HBase को और अधिक अनुकूल बनाने के लिए कक्षाएं लिखने में दिन बिता सकता हूं, और मुझे नहीं पता कि यह मेरे समय का एक बड़ा उपयोग है। इसके अलावा, फिर मैं सिर्फ पुराने पुराने HTable ऑब्जेक्ट के बजाय एक इंटरफ़ेस या रैपर के आसपास पासा कर रहा हूं, और यह निश्चित रूप से मेरे कोड को और अधिक जटिल बना देगा।
संगफ्राइड

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

मुझे पता नहीं है कि वास्तव में HTable क्या करता है और आप इसका उपयोग कैसे करते हैं, इसलिए मेरे आवरण उदाहरण को पत्र में न लें। मैंने एक रैपर / एडॉप्टर का उल्लेख किया था क्योंकि मुझे लगा कि रैप करने की चीज अपेक्षाकृत छोटी थी। आपको HTable के लिए एक-से-एक प्रतिकृति पेश करने की आवश्यकता नहीं है, जो निश्चित रूप से एक दर्द होगा, अकेले एक पूरे ढांचे को दें - लेकिन आपको अपने आवेदन के दायरे और HTable के बीच एक सीम , एक इंटरफ़ेस की आवश्यकता है। यह आपके आवेदन की अपनी शर्तों में HTable की कार्यक्षमता के कुछ rephrase चाहिए। रिपॉजिटरी पैटर्न डेटा के उपयोग के लिए इस तरह के सीम का एक आदर्श अवतार है।
guillaume31

11

दार्शनिक रूप से, मॉक का उपयोग करने वाले परीक्षणों को उन परीक्षणों पर प्राथमिकता देनी चाहिए जो लाइव एंडपॉइंट का उपयोग करते हैं

मुझे लगता है कि बहुत कम से कम, यह TDD समर्थकों के बीच मौजूदा चल रहे विवाद का एक बिंदु है ।

मेरा व्यक्तिगत विचार इस बात से परे है कि मॉक-आधारित परीक्षण ज्यादातर इंटरफ़ेस अनुबंध के एक रूप का प्रतिनिधित्व करने का एक तरीका है ; आदर्श रूप से यह टूट जाता है (यानी विफल रहता है) यदि और केवल यदि आप इंटरफ़ेस बदलते हैं । और इस तरह, जावा जैसी यथोचित रूप से दृढ़ता से टाइप की जाने वाली भाषाओं में, और स्पष्ट रूप से परिभाषित इंटरफ़ेस का उपयोग करते समय, यह लगभग पूरी तरह से अति-विशिष्ट है: कंपाइलर ने पहले ही आपको बता दिया होगा कि क्या आपने इंटरफ़ेस बदल दिया है।

मुख्य अपवाद तब है जब आप बहुत ही सामान्य इंटरफ़ेस का उपयोग कर रहे हैं, शायद एनोटेशन या प्रतिबिंब के आधार पर, कि संकलक अपने आप पुलिस को उपयोगी बनाने में सक्षम नहीं है। फिर भी आपको यह देखने के लिए जांचना चाहिए कि क्या मॉक का उपयोग करके हाथ की बजाय प्रोग्रामेटिकली (जैसे SQL सिंटैक्स चेकिंग लाइब्रेरी) सत्यापन करने का कोई तरीका है।

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

दुर्भाग्य से, मॉक-आधारित परीक्षण का एक बहुत अधिक सामान्य उपयोग यह परीक्षण है:

  • परीक्षा में उस समय जो भी कोड लिखा गया था, उसके लिए पास
  • कोड के किसी भी गुण के बारे में कोई गारंटी नहीं देता है कि यह मौजूद है और तरह के रन
  • किसी भी समय आप उस कोड को बदलते हैं

इस तरह के परीक्षणों को दृष्टि से हटा दिया जाना चाहिए।


1
मैं इसे पर्याप्त वापस नहीं कर सकता। मैं भराव के 100 pc कवरेज की तुलना में महान परीक्षणों के साथ 1 pc कवरेज चाहते हैं।
इयान

3
मॉक-आधारित परीक्षण वास्तव में एक साथ बात करने के लिए 2 ऑब्जेक्ट्स द्वारा उपयोग किए गए अनुबंध का वर्णन करते हैं, लेकिन वे जावा की तरह एक भाषा के प्रकार की प्रणाली से बहुत आगे निकल जाते हैं। यह केवल विधि हस्ताक्षरों के बारे में नहीं है, वे तर्कों या लौटाए गए परिणामों के लिए मान्य श्रेणियों की वैधता भी निर्दिष्ट कर सकते हैं, जो अपवाद स्वीकार्य हैं, किस क्रम में और कितनी बार विधियों को बुलाया जा सकता है, आदि। कंपाइलर अकेले आपको चेतावनी नहीं देगा अगर वहाँ उन में परिवर्तन कर रहे हैं। उस लिहाज से मुझे नहीं लगता कि वे बिल्कुल भी अनमने हैं। मॉक-आधारित परीक्षणों के बारे में अधिक जानकारी के लिए infoq.com/pretations/integration-tests-scam देखें ।
गुइलुमे ३१

1
... सहमत अर्थात् इंटरफेस कॉल के आसपास तर्क का परीक्षण
रोब

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

"उनका विनिर्देश निहित है": यदि आप अपने इंटरफेस ( blog.thecodewhisperer.com/2011/07/07/contract-tests-an-example ) के लिए अनुबंध परीक्षण लिखते हैं और मॉक सेट करते समय उनसे चिपके रहते हैं।
गुइलौमे 31

5

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


4

मैं पूरी तरह से guillaume31 की प्रतिक्रिया से सहमत हूं, कभी भी उन प्रकार का मजाक न करें जो आपके पास नहीं हैं!

आम तौर पर परीक्षण में एक दर्द (एक जटिल इंटरफ़ेस का मज़ाक करना) आपके डिज़ाइन में एक समस्या को दर्शाता है। शायद आपको अपने मॉडल और अपने डेटा एक्सेस कोड के बीच कुछ अमूर्तता की आवश्यकता होती है, एक हेक्सागोनल वास्तुकला और एक रिपॉजिटरी पैटर्न का उपयोग करके इस तरह की समस्याओं को हल करने का सबसे सामान्य तरीका है।

अगर आप चीजों की जाँच के लिए एक एकीकरण परीक्षण करना चाहते हैं, तो एक एकीकरण परीक्षण करते हैं, यदि आप एक इकाई परीक्षण करना चाहते हैं क्योंकि आप अपने तर्क का परीक्षण कर रहे हैं तो एक इकाई परीक्षण करें और डी दृढ़ता को अलग करें। लेकिन एक एकीकरण परीक्षण करना क्योंकि आप नहीं जानते कि किसी बाहरी प्रणाली से अपने तर्क को अलग कैसे किया जाए (या इसके दर्द को अलग करने के कारण) इसकी बड़ी गंध है, आप एक वास्तविक आवश्यकता के लिए अपने डिजाइन में एक सीमा के लिए इकाई पर एकीकरण का चयन कर रहे हैं एकीकरण का परीक्षण करने के लिए।

इस टॉक फॉर्म Ian कोऑपरेशन पर एक नज़र डालें: http://vimeo.com/68375232 , वह हेक्सागोनल आर्किटेक्चर और परीक्षण के बारे में बात करते हैं, वह इस बारे में बात करते हैं कि कब और क्या मज़ाक करना है, एक वास्तव में प्रेरित बात है जो आपके बारे में कई सवालों को हल करती है ।


1

टीएल; डीआर - जिस तरह से मैं इसे देखता हूं, यह इस बात पर निर्भर करता है कि आप परीक्षणों पर खर्च करने का कितना प्रयास करते हैं, और क्या इसके बजाय आपके वास्तविक सिस्टम पर इसे अधिक खर्च करना बेहतर होगा।

दीर्घ संस्करण:

यहाँ कुछ अच्छे उत्तर हैं, लेकिन मेरा इस पर ध्यान देना अलग है: परीक्षण एक आर्थिक गतिविधि है जिसे स्वयं के लिए वापस भुगतान करने की आवश्यकता होती है, और यदि आप जो समय बिताते हैं वह विकास और सिस्टम विश्वसनीयता में वापस नहीं आता है (या कुछ और जो आप बाहर निकलने के लिए देखते हैं। परीक्षण) तो आप एक बुरा निवेश कर सकते हैं; आप बिल्डिंग सिस्टम के व्यवसाय में हैं, परीक्षण नहीं लिख रहे हैं। इसलिए, परीक्षण लिखने और बनाए रखने के प्रयास को कम करना महत्वपूर्ण है।

उदाहरण के लिए, परीक्षण से मुझे मिलने वाले कुछ मुख्य मूल्य हैं:

  • विश्वसनीयता और इसलिए विकास की गति): रिफ्लेक्टर कोड / एक नए ढांचे को एकीकृत / एक अलग मंच के लिए एक घटक / बंदरगाह स्वैप, आश्वस्त रहें कि सामान अभी भी काम करता है
  • डिजाइन प्रतिक्रिया: क्लासिक TDD / BDD "आपके निम्न / मध्य-स्तरीय इंटरफेस पर आपके कोड" फीडबैक का उपयोग करें

एक लाइव समापन बिंदु के खिलाफ परीक्षण अभी भी इन प्रदान करना चाहिए।

लाइव समापन बिंदु के खिलाफ परीक्षण के लिए कुछ कमियां:

  • पर्यावरण सेटअप - परीक्षण चल रहे पर्यावरण को कॉन्फ़िगर और मानकीकृत करना अधिक काम है, और अलग-अलग पर्यावरणीय सेटअपों के परिणामस्वरूप अलग-अलग व्यवहार हो सकते हैं
  • स्टेटलेसनेस - लाइव एंडपॉइंट के खिलाफ काम करने से लेखन परीक्षण को बढ़ावा मिल सकता है जो एक म्यूटिंग एंडपॉइंट राज्य पर भरोसा करते हैं, जो कि नाजुक और कठिन है कारण के खिलाफ (यानी जब कुछ विफल हो रहा है, तो क्या यह अजीब स्थिति के कारण असफल हो रहा है?)
  • टेस्ट रनिंग का माहौल नाजुक है - अगर कोई टेस्ट फेल हो जाता है, तो क्या यह टेस्ट, कोड या लाइव एंडपॉइंट है?
  • रन गति - एक लाइव समापन बिंदु आमतौर पर धीमा होता है, और कभी-कभी समानांतर करना कठिन होता है
  • परीक्षण के लिए किनारे के मामले बनाना - आमतौर पर एक मॉक के साथ तुच्छ, कभी-कभी एक जीवित समापन बिंदु के साथ एक दर्द (उदाहरण के लिए ट्रिकी लोगों को परिवहन / HTTP त्रुटियां हैं)

यदि मैं इस स्थिति में था, और कमियां एक समस्या नहीं थी , जबकि समापन बिंदु का मज़ाक उड़ाते हुए मेरे परीक्षण लेखन को काफी धीमा कर दिया, मैं एक दिल की धड़कन में एक लाइव समापन बिंदु के खिलाफ परीक्षण करूंगा, जब तक कि मुझे यकीन होगा थोड़ी देर बाद फिर से देखें कि कमियां व्यवहार में समस्या तो नहीं हैं।


1

परीक्षण के दृष्टिकोण से कुछ आवश्यकताएं हैं जो एक पूर्ण होनी चाहिए:

  • परीक्षण (इकाई या अन्यथा) के पास उत्पादन डेटा को छूने का एक तरीका नहीं होना चाहिए
  • एक परीक्षण के परिणाम कभी भी दूसरे परीक्षण के परिणामों को प्रभावित नहीं करते हैं
  • आपको हमेशा एक ज्ञात स्थिति से शुरू करना चाहिए

किसी भी स्रोत से जुड़ते समय यह एक बड़ी चुनौती है जो आपके परीक्षणों के बाहर राज्य को बनाए रखता है। यह "शुद्ध" टीडीडी नहीं है, लेकिन रूबी क्रू पर रूबी ने इस मुद्दे को इस तरह से हल किया है कि आपके उद्देश्यों के लिए अनुकूलित किया जा सकता है। रेल परीक्षण रूपरेखा इस तरह से काम करती है:

  • इकाई परीक्षण चलाते समय परीक्षण कॉन्फ़िगरेशन स्वचालित रूप से चुना गया था
  • डेटाबेस का निर्माण और रनिंग यूनिट परीक्षणों की शुरुआत में किया गया था
  • यूनिट परीक्षण के बाद डेटाबेस को हटा दिया गया था
  • SqlLite का उपयोग करते समय, परीक्षण कॉन्फ़िगरेशन में RAM डेटाबेस का उपयोग किया जाता है

यह सब काम टेस्ट हार्नेस में बनाया गया था, और यह काफी अच्छी तरह से काम करता है। इसके लिए बहुत कुछ है, लेकिन इस बातचीत के लिए मूल बातें पर्याप्त हैं।

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

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.