Back to Question Center
0

RAML सह परीक्षण API            आरएएमएलशी संबंधित विषयांसह परीक्षण API डेटाबेसलॅवेल डीबगिंग आणि & उपयोजन डंपल विकास लघु

1 answers:
आरएएमएल सह चाचणी API

अलिकडच्या लेखात मी रीस्टाल्ट API मॉडेलिंग Semaltॅट (आरएएमएल) वर पाहिले. मी RAML हे कशाबद्दल आहे, ते कसे लिहावे आणि त्याचा काही वापर कसा करावा याचे एक अवलोकन प्रदान केले आहे

या वेळी, मी काही उपाय शोधणार आहे ज्यात आपण चाचणीसाठी RAML वापरू शकता. एपीआय कडून प्रतिसाद मान्य करण्यासाठी आरएएमएलचा वापर करुन प्रारंभ. मग आम्ही नकली HTTP प्रतिसाद तयार करण्यासाठी एक RAML फाइल वापरून, आपण API सर्व्हरला विनोद करण्याच्या दृष्टीकोणाकडे पाहू.

API वर प्रतिसादांचे प्रमाणीकरण

प्रथम, काल्पनिक API साठी सोपी RAML फाइल परिभाषित करा. Semaltेटने काही मार्ग सोडले, परंतु तत्त्वे दर्शविण्यासाठी ते पुरेसे असतील - logiciel gestion planning personnel gratuit.

  (1 9) #% आरएएमएल 0. 8शीर्षक: अल्बमआवृत्ती: v1baseUri: http: // लोकलहोस्ट: 8000विशेषता:- सुरक्षित:वर्णन: काही विनंत्यांसाठी प्रमाणीकरण आवश्यक आहेqueryParameters:accessToken:displayName: प्रवेश टोकनवर्णन: सुरक्षित मार्गांसाठी एक प्रवेश टोकन आवश्यक आहेआवश्यक: खरे- असुरक्षित:वर्णन: हे सुरक्षित नाही/ खाते:displayName: खातेमिळवा:वर्णन: सध्या प्रमाणित वापरकर्त्याचे खाते तपशील मिळवा. आहे: [सुरक्षित]प्रतिसाद:200:शरीर:अनुप्रयोग / JSON:स्कीमा: |{"$ स्कीमा": "http: // json-schema. org / स्कीमा #","प्रकार": "ऑब्जेक्ट","वर्णन": "एक वापरकर्ता","गुणधर्म": {"id": {"description": "या वापरकर्त्यासाठी अद्वितीय संख्यात्मक ID","प्रकार": "पूर्णांक"},"वापरकर्तानाव": {"वर्णन": "वापरकर्त्याचे वापरकर्तानाव","प्रकार": "स्ट्रिंग"},"ईमेल": {"वर्णन": "वापरकर्त्याचा ई-मेल पत्ता","प्रकार": "स्ट्रिंग","स्वरूप": "ईमेल"},"ट्विटर": {"वर्णन": "वापरकर्त्याचे ट्विटर स्क्रीन नाव (अग्रमापक @ शिवाय)","प्रकार": "स्ट्रिंग","मॅक्सलेॅन्थ": 15}},"आवश्यक": ["id", "वापरकर्तानाव"]}उदाहरण: |{"id": 12345678,"वापरकर्तानाव": "joebloggs","ईमेल": "joebloggs @ example.com","ट्विटर": "joebloggs"}ठेवा:वर्णन: वर्तमान वापरकर्त्याचे खाते अद्यतनित करा/ अल्बम:displayName: अल्बम/ {id}:displayName: अल्बमuriParameters:id:वर्णन: अंकीय ID जे अल्बमचे प्रतिनिधित्व करते/ ट्रॅक:displayName: अल्बम ट्रॅकलिस्ट करणेमिळवा:प्रतिसाद:200:शरीर:अनुप्रयोग / JSON:स्कीमा: |{"$ स्कीमा": "http: // json-schema. मिमलॅटने काही उपलब्ध मार्गांचे वर्णन केले आणि काही JSON स्किम्सने परिणामांचे स्वरूप निर्दिष्ट करण्यासाठी परिभाषित केले. मिमलमध्ये काही उदाहरणांचे प्रतिसाद देखील समाविष्ट होते; हे आम्ही विचित्र प्रतिसाद व्युत्पन्न करण्यासाठी वापरणार आहोत.  

चला एक ऍप्लिकेशन बनवू ज्याचा आपण या ट्युटोरियलच्या दोन्ही भागासाठी वापरू. आपल्याला ते गिथूबवर सापडेल.

या पहिल्या भागामध्ये साम्टट दाखवते की आपण RAML फाइलचे विश्लेषण कसे करू शकता, दिलेल्या मार्गासाठी स्कीमा काढू शकता आणि नंतर हे वापरून चाचणीसाठी वापरू शकता.

प्रोजेक्ट डिरेक्ट्री तयार करा, आणि फाइल test / fixture / api बनवा. रॅम वरील सामुग्रीसह

आम्ही API, PHPUnit एक चाचणी फ्रेमवर्क आणि या PHP- आधारित RAML विश्लेषक म्हणून प्रवेश करण्यासाठी Guzzle वापरणार आहोत. तर, एक संगीतकार तयार करा. या अवलंबितांची व्याख्या करण्यासाठी जेसन :

  (3 9) {"name": "sitepoint / raml-testing","वर्णन": "RAML परिभाषांच्या विरुद्ध चाचणी API चा एक सामान्य उदाहरण","आवश्यक": {"ऍलेक्समायन / पीएचपी-रैमल-पार्सर": "देव-मास्टर","गझले / गझल": "~ 3 9 @ देव","phpunit / phpunit": "~ 4 .6 @ देव"},"लेखक": [{"नाव": "लुकसाईट","ईमेल": "हॅलो @ लुकसाईट कॉम."}],"autoload": {"psr-0": {"साइटपॉइंट \\": "src /"}},"किमान-स्थिरता": "देव"}     

चालवा संगीतकार प्रतिष्ठापने आवश्यक संकुल डाउनलोड करण्यासाठी.

आता, एक साधी चाचणी तयार करूया जे एपीसी कडून प्रतिसाद मान्य करते. आम्ही एक सह सुरू करू phpunit xml फाइल:

              <निर्देशिका> . / चाचणी          <व्हाइटलिस्ट>  <डिरेक्टरी प्रत्यय = ". php"> . / src              

PHP RAML Parser सध्या एक नापसंती त्रुटी दर्शवित आहे याच्यावर जाण्यासाठी, आम्ही कन्व्हर्फरइपरर्सटो एक्सक्शप्शन , कन्वर्ट नोट्सटो एक्सक्शप्शन आणि कन्वर्टवर्विंग्स अपवाद ते फॉल्स .

चला एक कंकाल टेस्ट क्लास तयार करूया. हे नाव द्या चाचणी / अकाऊंट टेस्ट php , आणि setUp पद्धती परिभाषित करून प्रारंभ:

      {पॅरेंट :: सेट अप   ;$ parser = नवीन \ रामल \ पार्सर   ;$ the-> api = $ parseer-> parse (__ DIR__. '/ fixture / api. raml');$ routes = $ this-> api-> getResourcesAsUri    -> getRoutes   ;$ प्रतिसाद = $ मार्ग ['प्राप्त / खाते'] ['प्रतिसाद'] -> मिळवा रिझस्पॉन्स (200);$ the-> schema = $ response-> getSchemaByType ('application / json');}}     

येथे आपण RAML फाईलचे वाचन करत आहात, नंतर सर्व परिभाषित मार्ग काढत आहोत. पुढे आम्ही स्ट्रिंग GET / खाते द्वारे ओळखलेला मार्ग शोधून काढत आहोत. त्यावरून, आम्ही एक यशस्वी प्रतिसादांची व्याख्या काढत आहोत, आणि त्यावरून, आम्ही JSON स्किमा प्राप्त करीत आहोत जे JSON प्रतिसादाची अपेक्षित रचना परिभाषित करते.

आता आम्ही एक साधी चाचणी तयार करू शकतो ज्या आपल्या शेवटच्या बिंदूला कॉल करते, तपासते की आम्हाला 200 स्थिती मिळते, प्रतिसाद स्वरूप JSON आणि आहे जो स्कीमा विरूद्ध वैध आहे.

     / ** @ टेस्ट * /सार्वजनिक फंक्शन आवश्यक पाहिजे BeExpectedFormat   {$ accessToken = 'काही-गुप्त-टोकन';$ client = नवीन \ गेशल \ एचटीपी \ क्लाएंट   ;$ request = $ client-> मिळवा ($ the-> api-> getBaseUri   .  

सेमील्ट म्हणजे आपण आपल्या एपीआयची चाचणी घेण्यासाठी राएएमएलचा उपयोग करु शकता. तसेच JSON स्किमासांप्रमाणे, आरएएमएल एक्सएमएल स्कीमासदेखील समर्थन करतो- जेणेकरून एक्सएमएल स्वरूपातील परिणामांची तपासणी करण्याचे तत्व साधारणपणे समान असतील. आपण योग्य स्थिती कोड परत मिळवू शकता याची तपासणी करू शकता, आपल्या आरएएमएलमध्ये परिभाषित केलेल्या मार्ग अस्तित्वात आहेत आणि याप्रमाणे

पुढच्या भागामध्ये, आम्ही एपीएम प्रतिसाद विचित्र करण्यासाठी RAML वापरण्याचा विचार करू.

(9 5) आरएएमएल वापरून एपीआय मोकळा करणे

आरएएमएल बरोबर आम्ही आणखी एक सुबक काम करू शकतो. एक-आकार-फिट-सर्व उपहास करणारे वर्ग तयार करण्यासाठी विम्यास वेगवेगळ्या एपीआयमध्ये बर्याच फरक आहेत, तर आपण एक तयार करूया जे आपल्या गरजा पूर्ण करू शकते.

आपण काय करू तीन गोष्टी निर्माण केल्या आहेत:

  • ए "प्रतिसाद" वर्ग, ज्यामध्ये मानक HTTP प्रतिसाद डेटा, जसे की स्थिती कोड आणि शरीर
  • "URL" ला प्रत्युत्तर देण्यासाठी RAML चा वापर करणारे एक वर्ग
  • एक सोपी "सर्व्हर" जो आपण एखाद्या वेब सर्व्हरवर चालवू शकता

गोष्टी सोप्या ठेवण्यासाठी, आम्ही मागील विभागात समान कोड वापरु. आम्ही फक्त एक अतिरिक्त अवलंबन जोडण्यासाठी आवश्यक; FastRoute, एक साधी आणि जलद राउटिंग घटक जे आम्ही प्रतिसाद देणार आहोत ते मार्ग निश्चित करण्यासाठी वापरू. आपल्या संगीतकारांच्या कलम विभागात जोडा जेसन फाइल:

  (1 9) "निक्िक / जलद-मार्ग": "~ 0 3. 0"     

आता, एक खरोखर सोपे प्रतिसाद वर्ग तयार करूया; हे तयार करा src / Sitepoint / Response. php :

        स्थिती = $ स्थिती;$ the-> हेडर = ['सामग्री-प्रकार' => 'अनुप्रयोग / JSON'];}/ *** प्रतिसाद शरीर सेट करते** @ परम स्ट्रिंग $ body* /सार्वजनिक फंक्शन सेटबोडी ($ संस्था){$ this-> बॉडी = $ body;$ this-> शीर्षलेख ['सामग्री-लांबी'] = स्ट्रेलन ($ शरीर);}}     

येथे काहीही फार जटिल नाही. लक्षात घ्या की आम्ही एपीआयचे विनोद करणार आहोत जी केवळ "बोलू" JSON आहे, म्हणून आम्ही सामग्री-प्रकार ते पर्यंत अनुप्रयोग / जेएसन सक्ती करीत आहोत.

आता चला एक वर्ग प्रारंभ करूया जी HTTP क्रिया आणि पथ दिलेली आहे, एक जुळणारा मार्ग शोधण्यासाठी RAML फाइलकडे पहायला जाईल आणि योग्य प्रतिसाद परत करेल. आम्ही उचित प्रकाराच्या प्रतिसादावरून उदाहरण बाहेर खेचून करतो. या घटकांच्या हेतूसाठी, जे नेहमी यशस्वी होईल (स्थिती कोड 200 ) JSON प्रतिसाद.

फाइल तयार करा src / sitepoint / RamlApiMock. php , आणि खालीलसह वर्ग सुरू:

      ;$ api = $ parseer-> parse ($ ramlFilepath);// मार्ग काढा$ routes = $ api-> getResourcesAsUri    -> getRoutes   ;$ this-> मार्ग = $ मार्ग;// उपलब्ध मार्गांद्वारे पुनरावृत्ती करा आणि राउटरमध्ये जोडा$ this-> dispatcher = \ FastRoute \ simpleDispatcher (फंक्शन (\ फास्टआऊट \ रूटक्ललेटर $ आर) ($ मार्ग) वापरा {foreach ($ मार्ग म्हणून $ मार्ग) {$ r-> जोडा मार्ग ($ मार्ग ['पद्धत'], $ मार्ग ['पथ'], $ मार्ग ['पथ']);}});}}     

आपण येथे काय करत आहोत हे मिमल वाट पाहा.

पूर्वीप्रमाणे, आम्ही RAML फाईलचे विश्लेषण करीत आहोत. मग आम्ही उपलब्ध सर्व मार्ग काढत आहोत पुढील आम्ही त्यातून पुनरावृत्ती, आणि FastRoute घटक सुसंगत आहे जे संग्रह त्यांना जोडा. साधारणपणे, आपण त्यानुसार प्रत्येक मार्ग हाताळण्यासाठी वेगळी फंक्शन्स परिभाषित करता. तथापि, आम्ही समान कोड वापरून सर्व मार्ग हाताळण्यासाठी जात आहोत म्हणून, आम्ही हे वर्तन थोडासा अधिलिखित करत आहोत, आणि फंक्शन नावापेक्षा आम्ही दुसऱ्यांदा पथ जोडत आहोत. अशा प्रकारे, आम्ही पाठविण्याची गरज काय आहे हे निश्चित करण्यासाठी आमच्या हँडलरमधील मार्ग काढू शकतो.

चला एक प्रेषण पद्धत तयार करू.

     / *** एक मार्ग डिस्पेच** @param string $ पद्धत HTTP क्रिया (GET, POST इत्यादी)* @param स्ट्रिंग $ url URL* @ परम ऍरे $ डेटा डेटाची एक अॅरे (टीप, सध्या वापरले जात नाही)* @परम ऍरे $ शीर्षलेख हेडरचे अॅरे (टीप, सध्या वापरलेले नाही)* @ रिटर्न रिस्पॉन्स* /सार्वजनिक कार्य प्रेषण ($ पद्धत, $ url, $ data = array   , $ headers = array   ){// URL पार्स करा$ parseedUrl = parse_url ($ url);$ path = $ parseedUrl ['पथ'];// एक जुळणारा मार्ग मिळविण्याचा प्रयत्न$ routeInfo = $ this-> dispatcher-> प्रेषण ($ पद्धत, $ मार्ग);// मार्गाचे विश्लेषण करास्विच करा ($ routeInfo [0]) {केस \ फास्ट रवा \ डिस्पॅचर :: NOT_FOUND:// परत 404नवीन प्रतिसाद परत करा (404);ब्रेक;केस \ फास्टआउट \ डिस्पॅचर :: METHOD_NOT_ALLOWED:// पद्धती (405) परवानगी देत ​​नाही$ परवानगी पद्धती = मार्ग $ INFO [1];// प्रतिसाद तयार करा . $ प्रतिसाद = नवीन प्रतिसाद (405);// . आणि परवानगी शीर्षलेख सेट करा$ प्रतिसाद-> हेडर ['अनुमती द्या'] = अंतर्गोल (',', $ अनुमती दिलेल्या पद्धती);परत $ प्रतिसाद;ब्रेक;केस \ फास्टआउट \ डिस्पर्चर :: फोल्ड:$ हँडलर = $ routeInfo [1];$ var = $ routeInfo [2];$ हस्ताक्षर = स्प्रिंटफ ('% s% s', $ पद्धत, $ हँडलर);$ route = $ this-> मार्ग [$ हस्ताक्षर];// कोणतीही क्वेरी मापदंड मिळवाif (isset ($ parseedUrl ['query'])) {parse_str ($ parseedUrl ['query'], $ queryParams);} else {$ queryParams = [];}// चौकशी मापदंड तपासा$ errors = $ this-> checkQueryParameters ($ मार्ग, $ queryParams);जर (गणना ($ त्रुटी)) {$ प्रतिसाद = नवीन प्रतिसाद (400);$ प्रतिसाद-> सेटबॉडी (json_encode (['त्रुटी' => $ त्रुटी]));परत $ प्रतिसाद;}// आम्ही हे दूर केल्यास, एक यशस्वी प्रतिसाद आहेपरत $ the-> handleRoute ($ मार्ग, $ vars);ब्रेक;}}     

इथे काय चालले आहे?

आम्ही URL पार्स करणे आणि पथ काढणे सुरू करतो, नंतर प्रयत्न करून एक जुळणारा मार्ग शोधण्यासाठी मिमलचा वापर करा.

मार्ग संकलन चे डिस्पर्च पद्धत अॅरे मिळविते, त्याच्या पहिल्या घटकासह आम्हाला सांगते की ती एक वैध मार्ग आहे की नाही, जरी ती एक वैध मार्ग आहे परंतु अवैध पद्धत आहे किंवा सापडले नाही

जर आम्हाला जुळणारा मार्ग सापडत नसेल, तर आम्ही 404 न सापडलेले निर्माण करतो. जर पद्धत समर्थीत नसेल तर आम्ही 405 पद्धतीस परवानगी दिली नाही , योग्य पद्धतींना उचित शीर्षलेखमध्ये पॉप देत आहे.

तिसरे केस म्हणजे जिथे मनोरंजक होते. आम्ही पद्धत आणि मार्ग concatenating करून "स्वाक्षरी" व्युत्पन्न, म्हणून ते असे काहीतरी दिसते:

  (1 9) जीईटी / खाते     

किंवा:

     GET / अल्बम / {id} / ट्रॅक     

आम्ही नंतर $ routes प्रॉपर्टीतून मार्ग परिभाषा मिळवण्यासाठी तिचा वापर करू शकतो, ज्याला आपण आपल्या आरएएमएल फाइलमधून काढले आहे हे लक्षात येईल.

पुढील चरण म्हणजे क्वेरी मापदंडांच्या अर्रे तयार करणे, आणि नंतर फंक्शन कॉल करते जे त्यांना तपासते - आम्ही एका क्षणात त्या विशिष्ट फंक्शनवर येऊ. वेगवेगळ्या एपीआयच्या त्रुटी वेगवेगळ्या प्रकारे हाताळू शकतात कारण आपण हे आपल्या एपीआयनुसार बदलू शकता - या उदाहरणात मी फक्त 400 खराब विनंती परत करीत आहे, ज्यामध्ये विशिष्ट प्रमाणीकरणाची JSON प्रतिनिधी आहे त्रुटी

या ठिकाणी, आपण काही अतिरिक्त चेक किंवा सत्यापन जोडण्याची इच्छा असू शकते. आपण हे करू शकता, उदाहरणार्थ, विनंतीमध्ये योग्य सुरक्षा क्रेडेन्शियल प्रदान केलेल्या आहेत किंवा नाही हे तपासा.

शेवटी, आम्ही हॅंडरॉउट पद्धत म्हणतो, मार्ग परिभाषा आणि कोणत्याही URI पॅरामीटर पारित करणे. आपण बघितण्या अगोदर, आपल्या क्वेरी मापदंडाच्या प्रमाणीकरणाकडे परत जाऊया.

     / *** कोणतेही क्वेरी मापदंड तपासते* @परम ऍरे $ रुर RAML मधून घेतलेली वर्तमान मार्ग परिभाषा* @परम अरे $ params क्वेरी पॅरामीटर्स* @ रुटन बूलियन* /सार्वजनिक कार्य तपासणीअनुरूपता ($ मार्ग, $ परिमाण){// या मार्गाचा उपलब्ध क्वेरी मापदंड मिळवा$ queryParameters = $ route ['प्रतिसाद'] -> getQueryParameters   ;// त्रुट्यांना ठेवण्यासाठी अॅरे तयार करा$ errors = [];जर (गणना ($ queryParameters)) {foreach ($ queryParameters as $ name => $ परम) {// जर डिस्प्ले नाव सेट केले असेल, तर आपण ते वापरू - अन्यथा आम्ही वापरु// नाव$ displayName = (strlen ($ param-> getDisplayName   ))? $ param-> getDisplayName   : $ name;// पॅरामीटर आवश्यक असल्यास परंतु पुरविले नसल्यास, त्रुटी जोडाजर ($ param-> isRequired    &&! isset ($ params [$ name])) {$ errors [$ name] = sprintf ('% s आवश्यक आहे', $ displayName);}// आता स्वरूप तपासाजर (isset ($ params [$ name])) {स्विच करा ($ param-> getType   ) {केस 'स्ट्रिंग':जर (! is_string ($ params [$ name])) {$ errors [$ name] = sprintf ('% s हा स्ट्रिंग असणे आवश्यक आहे');}ब्रेक;केस नंबर':if (! is_numeric ($ params [$ name])) {$ errors [$ name] = sprintf ('% s हा एक अंक असणे आवश्यक आहे');}ब्रेक;केस 'पूर्णांक':जर (! आयआयआयटी ($ params [$ name])) {$ errors [$ name] = sprintf ('% s हा पूर्णांक असणे आवश्यक आहे');}ब्रेक;केस 'बूलियन':जर (! is_bool ($ params [$ name])) {$ errors [$ name] = sprintf ('% s बुलियन असणे आवश्यक आहे');}ब्रेक;// तारीख आणि फाईल थोडक्यात वगळली जातात}}}}// अखेरीस, त्रुटी परत करा$ errors परत;}     

ही अतिशय सोपी गोष्ट आहे - आणि लक्षात घ्या की Semaltेटने काही सोपे घटक ठेवण्यासाठी पॅरामीटर प्रकार सोडले आहेत - परंतु हे क्वेरी मापदंडांसोबत खेळण्यासाठी वापरले जाऊ शकते आणि अशा पॅकेजेसमध्ये नमूद केलेल्या वैशिष्ट्यानुसार फिट नसतात आमची RAML फाइल

शेवटी, हँडलरेट फंक्शन्स:

     / *** दिलेल्या मार्गासाठी प्रतिसाद परत करा** @परम ऍरे $ रुर RAML मधून घेतलेली वर्तमान मार्ग परिभाषा* @परम अरे $ vars URI पॅरामीटर्सची एक वैकल्पिक अरे* @ रिटर्न रिस्पॉन्स* /सार्वजनिक फंक्शन handleRoute ($ मार्ग, $ vars){// एक प्रतिरूप तयार करा$ प्रतिसाद = नवीन प्रतिसाद (200);// RAML मधून एक उदाहरण प्रतिसाद परत करा$ प्रतिसाद-> सेटबॉडी ($ मार्ग ['प्रतिसाद'] -> मिळवा रिस्पॉन्स (200) -> getExampleByType ('application / json'));// आणि निकाल परत करापरत $ प्रतिसाद;}     

आम्ही येथे काय करीत आहोत ते उचित मार्गावरूनचे उदाहरण काढत आहे, आणि 200 9 च्या स्टेटस कोडसह प्रतिसाद म्हणून ते परत करत आहे.

या टप्प्यावर, आपण आपल्या युनिट चाचण्यांमध्ये RamlApiMock वापरू शकता. तथापि, एक सोपा वाढीसह, आम्ही एक वेब सेवा म्हणून हा उपहास घटक प्रदान करू शकतो, फक्त काही सोयीचे रूटिंग लॉजिकसह कॉल जोडून.

हे करण्यासाठी, अनुक्रमणिका तयार करा. php फाइल खालील सामुग्रीसह:

       

आता सर्व्हर खालील चालवा:

  (1 9) php -S लोकलहोस्ट: 8000     

कारण एपीआयमध्ये बर्याच प्रमाणात फरक आहे, त्यामुळे आपल्या कार्यान्वयनासाठी हे उदाहरण आपल्याला सुधारणे आवश्यक आहे. सममूल्य, ते आपल्याला प्रारंभ करण्यासाठी पुरेसा देते.

सारांश

या लेखातील क्षेमॅटने परीक्षण आणि उपहास API च्या संदर्भात आरएएमएलकडे पाहिले.

Semaltेट आरएएमएल हे एक अप्रत्यक्ष आणि सर्वसमावेशक विधान प्रदान करते की एपीआय कशा प्रकारे कार्य करेल, विरोधात परीक्षणासाठी आणि उपहासाने प्रतिसाद देण्यासाठी हे अतिशय उपयुक्त आहे.

RAML सह आपण बरेच काही करू शकता, आणि ही उदाहरणे केवळ आरएएमएलच्या परीक्षणात कसे वापरली जाऊ शकतात यावरील पृष्ठभागास स्पर्श करतात परंतु आशा आहे की Semaltॅटने काही कल्पना आपल्याला प्रदान केल्या.

March 1, 2018