गो में मॉक फंक्शन


147

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

तुच्छ इकाई परीक्षण सभी ठीक और बांका था, लेकिन मैं अब निर्भरता से हैरान हूँ; मैं कुछ फंक्शन कॉल को नकली वाले के साथ बदलने में सक्षम होना चाहता हूं। यहाँ मेरे कोड का एक स्निपेट है:

func get_page(url string) string {
    get_dl_slot(url)
    defer free_dl_slot(url)

    resp, err := http.Get(url)
    if err != nil { return "" }
    defer resp.Body.Close()

    contents, err := ioutil.ReadAll(resp.Body)
    if err != nil { return "" }
    return string(contents)
}

func downloader() {
    dl_slots = make(chan bool, DL_SLOT_AMOUNT) // Init the download slot semaphore
    content := get_page(BASE_URL)
    links_regexp := regexp.MustCompile(LIST_LINK_REGEXP)
    matches := links_regexp.FindAllStringSubmatch(content, -1)
    for _, match := range matches{
        go serie_dl(match[1], match[2])
    }
}

मैं वास्तव में http के माध्यम से एक पृष्ठ प्राप्त किए बिना डाउनलोडर () का परीक्षण करने में सक्षम होना चाहता हूं - अर्थात या तो get_page (जब से यह केवल पृष्ठ सामग्री को एक स्ट्रिंग के रूप में लौटाता है) या http.Get ()।

मुझे यह धागा मिला: https://groups.google.com/forum/# .topic/ golang- nuts/ 6AN1E2CJOxI जो एक समान समस्या के बारे में लगता है। जूलियन फिलिप्स अपनी लाइब्रेरी, Withmock ( http://github.com/qur/withmock ) को एक समाधान के रूप में प्रस्तुत करता है , लेकिन मैं इसे काम करने में असमर्थ पा रहा हूं। यहाँ मेरे परीक्षण कोड के प्रासंगिक भाग हैं, जो ईमानदार होने के लिए बड़े पैमाने पर कार्गो पंथ कोड है:

import (
    "testing"
    "net/http" // mock
    "code.google.com/p/gomock"
)
...
func TestDownloader (t *testing.T) {
    ctrl := gomock.NewController()
    defer ctrl.Finish()
    http.MOCK().SetController(ctrl)
    http.EXPECT().Get(BASE_URL)
    downloader()
    // The rest to be written
}

परीक्षण आउटपुट निम्न है:

ERROR: Failed to install '_et/http': exit status 1
output:
can't load package: package _et/http: found packages http (chunked.go) and main (main_mock.go) in /var/folders/z9/ql_yn5h550s6shtb9c5sggj40000gn/T/withmock570825607/path/src/_et/http

Withmock मेरी परीक्षण समस्या का समाधान है? मुझे इसे काम करने के लिए क्या करना चाहिए?


जब से आप गो इकाई परीक्षण में गोताखोरी कर रहे हैं, व्यवहार-चालित परीक्षण करने के लिए एक शानदार तरीके से GoConvey में देखें ... और टीज़र: एक स्वचालित रूप से अपडेट होने वाला वेब UI आ रहा है जो मूल "गो टेस्ट" परीक्षणों के साथ भी काम करता है।
मैट

जवाबों:


193

अच्छा परीक्षण का अभ्यास करने के लिए आप के लिए यश! :)

व्यक्तिगत रूप से, मैं gomockउस मामले के लिए (या किसी भी मज़ाकिया ढांचे का उपयोग नहीं करता हूं ; गो में नकल करना इसके बिना बहुत आसान है)। मैं या तो downloader()एक पैरामीटर के रूप में फ़ंक्शन के लिए एक निर्भरता पारित करूंगा , या मैं downloader()एक प्रकार पर एक विधि बनाऊंगा, और प्रकार get_pageनिर्भरता को पकड़ सकता है:

विधि 1: के get_page()पैरामीटर के रूप में पास करेंdownloader()

type PageGetter func(url string) string

func downloader(pageGetterFunc PageGetter) {
    // ...
    content := pageGetterFunc(BASE_URL)
    // ...
}

मुख्य:

func get_page(url string) string { /* ... */ }

func main() {
    downloader(get_page)
}

परीक्षा:

func mock_get_page(url string) string {
    // mock your 'get_page()' function here
}

func TestDownloader(t *testing.T) {
    downloader(mock_get_page)
}

Method2: download()एक विधि का एक प्रकार बनाओ Downloader:

यदि आप एक पैरामीटर के रूप में निर्भरता को पारित नहीं करना चाहते हैं, तो आप get_page()एक प्रकार का सदस्य भी बना सकते हैं , और download()उस प्रकार की एक विधि बना सकते हैं, जो तब उपयोग कर सकते हैं get_page:

type PageGetter func(url string) string

type Downloader struct {
    get_page PageGetter
}

func NewDownloader(pg PageGetter) *Downloader {
    return &Downloader{get_page: pg}
}

func (d *Downloader) download() {
    //...
    content := d.get_page(BASE_URL)
    //...
}

मुख्य:

func get_page(url string) string { /* ... */ }

func main() {
    d := NewDownloader(get_page)
    d.download()
}

परीक्षा:

func mock_get_page(url string) string {
    // mock your 'get_page()' function here
}

func TestDownloader() {
    d := NewDownloader(mock_get_page)
    d.download()
}

4
आपका बहुत बहुत धन्यवाद! मैं दूसरे के साथ चला गया। (कुछ अन्य कार्य भी थे जिनका मैं मजाक करना चाहता था, इसलिए उन्हें एक संरचना में निर्दिष्ट करना आसान था) Btw। मुझे गो में थोड़ा प्यार है। विशेष रूप से इसकी संगामिति विशेषताएँ साफ-सुथरी हैं!
गोलड्रेक्स

149
क्या मैं केवल एक ही खोज हूं कि परीक्षण के लिए हमें मुख्य कोड / फ़ंक्शंस को बदलना होगा हस्ताक्षर भयानक है?
थॉमस

41
@ थोमस मुझे यकीन नहीं है कि यदि आप केवल एक हैं, लेकिन यह वास्तव में परीक्षण संचालित विकास का मूल कारण है - आपका परीक्षण आपके उत्पादन कोड को लिखने के तरीके को निर्देशित करता है। परीक्षण योग्य कोड अधिक मॉड्यूलर है। इस मामले में, डाउनलोडर ऑब्जेक्ट का 'गेट_पेज' व्यवहार अब प्लग करने योग्य है - हम गतिशील रूप से इसके कार्यान्वयन को बदल सकते हैं। आपको केवल अपना मुख्य कोड बदलना होगा यदि यह पहली बार में बुरी तरह से लिखा गया था।
वेबर 2

21
@ थोमस मैं आपका दूसरा वाक्य नहीं समझता। TDD बेहतर कोड चलाता है। परीक्षण योग्य होने के लिए आपका कोड बदल जाता है (क्योंकि परीक्षण योग्य कोड आवश्यक रूप से अच्छी तरह से सोचे गए इंटरफेस के साथ मॉड्यूलर है), लेकिन प्राथमिक उद्देश्य बेहतर कोड होना है - स्वचालित परीक्षण होना केवल एक भयानक माध्यमिक लाभ है। यदि आपकी चिंता यह है कि इस तथ्य के बाद परीक्षणों को जोड़ने के लिए बस कार्यात्मक कोड को बदला जा रहा है, तो मैं अभी भी इसे बदलने की सिफारिश करूंगा क्योंकि किसी दिन यह संभावना है कि कोई व्यक्ति उस कोड को पढ़ना चाहेगा या उसे बदल देगा।
weberc2

6
@ थोमस बेशक, अगर आप अपने परीक्षण लिख रहे हैं तो आप साथ चलते हैं, आपको उस पहेली से निपटना नहीं पड़ेगा।
weberc2

24

यदि आप इसके बजाय एक चर का उपयोग करने के लिए अपनी फ़ंक्शन परिभाषा बदलते हैं:

var get_page = func(url string) string {
    ...
}

आप इसे अपने परीक्षणों में ओवरराइड कर सकते हैं:

func TestDownloader(t *testing.T) {
    get_page = func(url string) string {
        if url != "expected" {
            t.Fatal("good message")
        }
        return "something"
    }
    downloader()
}

हालांकि, आपके अन्य परीक्षण विफल हो सकते हैं यदि वे उस फ़ंक्शन की कार्यक्षमता का परीक्षण करते हैं जो आप ओवरराइड करते हैं!

गो लेखक इस पैटर्न का उपयोग गो मानक पुस्तकालय में परीक्षण हुक सम्मिलित करने के लिए चीजों को परीक्षण के लिए आसान बनाने के लिए करते हैं:

https://golang.org/src/net/hook.go

https://golang.org/src/net/dial.go#L248

https://golang.org/src/net/dial_test.go#L701


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

4
ध्यान देने वाली बात यह है कि फ़ंक्शन इस तरह से पुनरावर्ती नहीं हो सकता है।
बेन सैंडलर

2
मैं @ जेक से सहमत हूं कि इस दृष्टिकोण का अपना स्थान है।
m.kocikowski

11

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

इसे समझने के लिए यह समझना अत्यावश्यक है कि आपके पास आपके परीक्षण मामले (यानी आपकी _test.goफाइलों के भीतर से ) में गैर-पंजीकृत तरीकों तक पहुंच है, इसलिए आप निर्यात किए गए लोगों का परीक्षण करने के बजाय उन लोगों का परीक्षण करते हैं जिनके पास लपेटने के अंदर कोई तर्क नहीं है।

सारांशित करने के लिए: निर्यात किए गए लोगों के परीक्षण के बजाय अस्पष्टीकृत कार्यों का परीक्षण करें!

आइए एक उदाहरण बनाते हैं। कहें कि हमारे पास एक सुस्त एपीआई संरचना है जिसमें दो तरीके हैं:

  • वह SendMessageतरीका जो एक HTTP अनुरोध को स्लैक वेबहुक पर भेजता है
  • जिस SendDataSynchronouslyपद्धति ने तार का एक टुकड़ा दिया, वह उनके ऊपर SendMessageपुनरावृत्त करता है और प्रत्येक पुनरावृत्ति के लिए कहता है

तो SendDataSynchronouslyहर बार हमें HTTP अनुरोध किए बिना परीक्षण करने के लिए हमें SendMessageसही, सही करना होगा?

package main

import (
    "fmt"
)

// URI interface
type URI interface {
    GetURL() string
}

// MessageSender interface
type MessageSender interface {
    SendMessage(message string) error
}

// This one is the "object" that our users will call to use this package functionalities
type API struct {
    baseURL  string
    endpoint string
}

// Here we make API implement implicitly the URI interface
func (api *API) GetURL() string {
    return api.baseURL + api.endpoint
}

// Here we make API implement implicitly the MessageSender interface
// Again we're just WRAPPING the sendMessage function here, nothing fancy 
func (api *API) SendMessage(message string) error {
    return sendMessage(api, message)
}

// We want to test this method but it calls SendMessage which makes a real HTTP request!
// Again we're just WRAPPING the sendDataSynchronously function here, nothing fancy
func (api *API) SendDataSynchronously(data []string) error {
    return sendDataSynchronously(api, data)
}

// this would make a real HTTP request
func sendMessage(uri URI, message string) error {
    fmt.Println("This function won't get called because we will mock it")
    return nil
}

// this is the function we want to test :)
func sendDataSynchronously(sender MessageSender, data []string) error {
    for _, text := range data {
        err := sender.SendMessage(text)

        if err != nil {
            return err
        }
    }

    return nil
}

// TEST CASE BELOW

// Here's our mock which just contains some variables that will be filled for running assertions on them later on
type mockedSender struct {
    err      error
    messages []string
}

// We make our mock implement the MessageSender interface so we can test sendDataSynchronously
func (sender *mockedSender) SendMessage(message string) error {
    // let's store all received messages for later assertions
    sender.messages = append(sender.messages, message)

    return sender.err // return error for later assertions
}

func TestSendsAllMessagesSynchronously() {
    mockedMessages := make([]string, 0)
    sender := mockedSender{nil, mockedMessages}

    messagesToSend := []string{"one", "two", "three"}
    err := sendDataSynchronously(&sender, messagesToSend)

    if err == nil {
        fmt.Println("All good here we expect the error to be nil:", err)
    }

    expectedMessages := fmt.Sprintf("%v", messagesToSend)
    actualMessages := fmt.Sprintf("%v", sender.messages)

    if expectedMessages == actualMessages {
        fmt.Println("Actual messages are as expected:", actualMessages)
    }
}

func main() {
    TestSendsAllMessagesSynchronously()
}

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

चीजों को आसान बनाने के लिए मैंने आपको एक फ़ाइल में सब कुछ डाल दिया, ताकि आप यहां खेल के मैदान में कोड को चला सकें, लेकिन मेरा सुझाव है कि आप GitHub पर पूरा उदाहरण देखें, यहाँ slack.go फ़ाइल है और यहाँ slack_test.go है

और यहाँ पूरी बात :)


यह वास्तव में एक दिलचस्प दृष्टिकोण है और टेस्ट फाइल में निजी तरीकों तक पहुंच के बारे में tidbit वास्तव में उपयोगी है। यह मुझे C ++ में pimpl तकनीक की याद दिलाता है। हालांकि, मुझे लगता है कि यह कहा जाना चाहिए कि निजी कार्यों का परीक्षण खतरनाक है। निजी सदस्यों को आमतौर पर कार्यान्वयन विवरण माना जाता है और सार्वजनिक इंटरफ़ेस की तुलना में समय के साथ बदलने की अधिक संभावना है। जब तक आप केवल सार्वजनिक इंटरफ़ेस के आसपास निजी रैपर का परीक्षण करते हैं, हालांकि, आपको ठीक होना चाहिए।
c1moore

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

हाँ मै सह्मत हूँ। मैं सिर्फ यह कह रहा था कि जब तक आप इसे निजी तरीकों तक सीमित रखते हैं, जो उन सार्वजनिक लोगों को लपेटते हैं, तो आपको अच्छा होना चाहिए। बस उन निजी तरीकों का परीक्षण शुरू न करें जो कार्यान्वयन विवरण हैं।
c1moore

7

मैं कुछ ऐसा करूंगा,

मुख्य

var getPage = get_page
func get_page (...

func downloader() {
    dl_slots = make(chan bool, DL_SLOT_AMOUNT) // Init the download slot semaphore
    content := getPage(BASE_URL)
    links_regexp := regexp.MustCompile(LIST_LINK_REGEXP)
    matches := links_regexp.FindAllStringSubmatch(content, -1)
    for _, match := range matches{
        go serie_dl(match[1], match[2])
    }
}

परीक्षा

func TestDownloader (t *testing.T) {
    origGetPage := getPage
    getPage = mock_get_page
    defer func() {getPage = origGatePage}()
    // The rest to be written
}

// define mock_get_page and rest of the codes
func mock_get_page (....

और मैं _गोलंग में टाल देता। ऊंट का बेहतर इस्तेमाल करें


1
क्या ऐसा पैकेज विकसित करना संभव होगा जो आपके लिए ऐसा कर सके। मैं की तरह कुछ सोच रहा हूँ: p := patch(mockGetPage, getPage); defer p.done()। मैं जाने के लिए नया हूं, और unsafeपुस्तकालय का उपयोग करके ऐसा करने की कोशिश कर रहा था , लेकिन सामान्य मामले में ऐसा करना असंभव लगता है।
विटिरल

@ जब यह मेरा एक वर्ष के बाद लिखा गया लगभग मेरा उत्तर है।
जेक

1
1. एकमात्र समानता वैश्विक रूप तरीका है। @ जेक 2. कॉम्प्लेक्स की तुलना में सरल बेहतर है। weberc2
गिर गया

1
@ उदाहरण मैं आपके उदाहरण को सरल नहीं मानता। दलीलें पास करना तर्क वैश्विक राज्य को बदलने से अधिक जटिल नहीं है, लेकिन वैश्विक राज्य पर निर्भर होने से बहुत सारी समस्याएं होती हैं जो अन्यथा मौजूद नहीं होती हैं। उदाहरण के लिए, यदि आप अपने परीक्षणों को समानांतर बनाना चाहते हैं, तो आपको दौड़ की स्थितियों से निपटना होगा।
वेबर 2

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

0

चेतावनी: यह निष्पादन योग्य फ़ाइल के आकार को थोड़ा बढ़ा सकता है और थोड़ा रनटाइम प्रदर्शन खर्च कर सकता है। IMO, यह बेहतर होगा यदि गोलंग में मैक्रो या फ़ंक्शन डेकोरेटर जैसी सुविधा हो।

यदि आप इसके एपीआई को बदले बिना कार्यों का मजाक बनाना चाहते हैं, तो सबसे आसान तरीका यह है कि कार्यान्वयन को थोड़ा बदल दिया जाए:

func getPage(url string) string {
  if GetPageMock != nil {
    return GetPageMock()
  }

  // getPage real implementation goes here!
}

func downloader() {
  if GetPageMock != nil {
    return GetPageMock()
  }

  // getPage real implementation goes here!
}

var GetPageMock func(url string) string = nil
var DownloaderMock func() = nil

इस तरह हम वास्तव में एक फंक्शन को दूसरों से अलग कर सकते हैं। अधिक सुविधाजनक के लिए हम इस तरह के नकली बॉयलरप्लेट प्रदान कर सकते हैं:

// download.go
func getPage(url string) string {
  if m.GetPageMock != nil {
    return m.GetPageMock()
  }

  // getPage real implementation goes here!
}

func downloader() {
  if m.GetPageMock != nil {
    return m.GetPageMock()
  }

  // getPage real implementation goes here!
}

type MockHandler struct {
  GetPage func(url string) string
  Downloader func()
}

var m *MockHandler = new(MockHandler)

func Mock(handler *MockHandler) {
  m = handler
}

परीक्षण फ़ाइल में:

// download_test.go
func GetPageMock(url string) string {
  // ...
}

func TestDownloader(t *testing.T) {
  Mock(&MockHandler{
    GetPage: GetPageMock,
  })

  // Test implementation goes here!

  Mock(new(MockHandler)) // Reset mocked functions
}

-2

यूनिट टेस्ट को ध्यान में रखते हुए इस सवाल का डोमेन है, अत्यधिक आपको https://github.com/bouk/monkey का उपयोग करने की सलाह देते हैं । यह पैकेज आपको अपने मूल स्रोत कोड को बदलने के बिना परीक्षण का मजाक बनाने के लिए बनाता है। अन्य उत्तर की तुलना में, यह अधिक "गैर-घुसपैठ" है it's

मुख्य

type AA struct {
 //...
}
func (a *AA) OriginalFunc() {
//...
}

नकली परीक्षण

var a *AA

func NewFunc(a *AA) {
 //...
}

monkey.PatchMethod(reflect.TypeOf(a), "OriginalFunc", NewFunc)

बुरा पक्ष है:

- Dave.C द्वारा याद दिलाया गया, यह तरीका असुरक्षित है। इसलिए यूनिट टेस्ट के बाहर इसका इस्तेमाल न करें।

- नॉन-आइडियल गो है।

अच्छा पक्ष है:

++ गैर-घुसपैठ है। आप मुख्य कोड को बदले बिना चीजें बनाते हैं। जैसे थॉमस ने कहा।

++ आप कम से कम कोड के साथ पैकेज का व्यवहार बदल सकते हैं (शायद तीसरे पक्ष द्वारा प्रदान किया गया है)।


1
कृपया ऐसा न करें। यह पूरी तरह से असुरक्षित है और विभिन्न गो इंटर्नल्स को तोड़ सकता है। नहीं उल्लेख करने के लिए यह भी दूर से मुहावरेदार नहीं है।
डेव सी

1
@DaveC मैं गोलांग के बारे में आपके अनुभव का सम्मान करता हूं, लेकिन आपकी राय पर संदेह है। 1. सुरक्षा का मतलब सॉफ्टवेयर डेवलपमेंट से नहीं है, फीचर से भरपूर और सुविधा से है। 2. मुहावरेदार गोलंग गोलंग नहीं है, इसका हिस्सा है। यदि एक परियोजना ओपन-सोर्स है, तो अन्य लोगों के लिए उस पर गंदा खेलना आम है। समुदाय को इसे कम से कम इसे दबाने के लिए प्रोत्साहित करना चाहिए।
फ्रैंक वांग

2
भाषा को गो कहा जाता है। असुरक्षित से मेरा मतलब है कि यह गो रनटाइम को तोड़ सकता है, कचरा संग्रह जैसी चीजें।
डेव सी

1
मेरे लिए, असुरक्षित एक इकाई परीक्षण के लिए अच्छा है। अगर यूनिट टेस्ट के लिए हर बार अधिक 'इंटरफेस' वाले रीफैक्टरिंग कोड की जरूरत होती है। यह मुझे अधिक फिट बैठता है कि इसे हल करने के लिए असुरक्षित तरीके का उपयोग करें।
फ्रैंक वांग

1
@ मैं पूरी तरह से सहमत हूँ कि यह एक बहुत अच्छा विचार है (मेरा जवाब शीर्ष-मतदान और स्वीकृत उत्तर होने के नाते), लेकिन पांडित्यपूर्ण होने के लिए मुझे नहीं लगता कि इससे जीसी टूटेगा क्योंकि गो जीसी रूढ़िवादी है और इस तरह के मामलों को संभालने के लिए है। हालांकि, मुझे सही होने में खुशी होगी।
weberc2
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.