2009-06-11 10 views
32

मान लीजिए कि मैं एक हास्केल फ़ंक्शन f (या तो शुद्ध या एक क्रिया) को परिभाषित कर रहा हूं और कहीं f f कॉल फंक्शन जी के भीतर। उदाहरण के लिए:हास्केल में परीक्षण के लिए कैसे नकल करें?

f = ... 
    g someParms 
    ... 

मैं इकाई परीक्षण के लिए एक नकली संस्करण के साथ फ़ंक्शन जी को कैसे बदलूं?

यदि मैं जावा में काम कर रहा था, तो जी SomeServiceImpl पर एक विधि होगी जो इंटरफ़ेस SomeService लागू करता है। फिर, मैं SomeServiceImpl या MockSomeServiceImpl का उपयोग करने के लिए एफ को बताने के लिए निर्भरता इंजेक्शन का उपयोग करूंगा। मुझे यकीन नहीं है कि हास्केल में यह कैसे करें।

class SomeService a where 
    g :: a -> typeOfSomeParms -> gReturnType 

data SomeServiceImpl = SomeServiceImpl 
data MockSomeServiceImpl = MockSomeServiceImpl 

instance SomeService SomeServiceImpl where 
    g _ someParms = ... -- real implementation of g 

instance SomeService MockSomeServiceImpl where 
    g _ someParms = ... -- mock implementation of g 

फिर, च के रूप में निम्नानुसार को फिर से परिभाषित:

यह एक प्रकार वर्ग SomeService लागू करने के लिए क्या करने के लिए सबसे अच्छा तरीका है

f someService ... = ... 
        g someService someParms 
        ... 

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

संपादित करें:

के इस एक कदम आगे ले चलते हैं। मान लीजिए कि मेरे पास एक मॉड्यूल में ए, बी, सी, डी, ई, और एफ की एक श्रृंखला है, जो सभी को एक अलग मॉड्यूल से कार्यों g, h, i, और j संदर्भित करने में सक्षम होना चाहिए। और मान लीजिए कि मैं फ़ंक्शन g, h, i, और j mock करने में सक्षम होना चाहता हूं। मैं स्पष्ट रूप से 4 कार्यों को ए-एफ के पैरामीटर के रूप में पास कर सकता हूं, लेकिन यह सभी कार्यों में 4 पैरामीटर जोड़ने के लिए थोड़ा दर्द है। इसके अलावा, अगर मुझे कभी भी किसी अन्य विधि को कॉल करने के लिए ए-एफ के कार्यान्वयन को बदलने की आवश्यकता है, तो मुझे इसके हस्ताक्षर को बदलने की आवश्यकता होगी, जो एक बुरा रिफैक्टरिंग व्यायाम कर सकता है।

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

+3

क्यों शुद्ध कार्यों का नकल करना चाहते हैं? – yairchu

+0

अच्छा बिंदु, yairchu। आप शायद केवल नकली कार्रवाई करेंगे। –

+3

@yairchu मैं इसे दक्षता कारणों से करूँगा। क्या उत्पादकता के लिए एक्स या 100x समय लेना परीक्षण बेहद महत्वपूर्ण है। तो मैं परीक्षण उद्देश्यों के लिए फैक्टरियल 100000000 = <कुछ बड़ी संख्या> कहना चाहूंगा (इस प्रकार इनपुट/नकली के रूप में अन्य परीक्षण)। लेकिन उस फैक्टरियल 100000000 = <कुछ बड़ी संख्या> टेस्ट रनर फैक्टरियल मॉड्यूल पर चलने पर खुद में एक परीक्षण हो सकती है। – user239558

उत्तर

20

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

"निर्भरता इंजेक्शन" अंतर्निहित पैरामीटर गुजरने का एक अपमानजनक रूप है। हास्केल में, आप बहुत कम गड़बड़ी के साथ एक ही चीज़ को प्राप्त करने के लिए बस Reader, या Free का उपयोग कर सकते हैं।

+4

' पर निर्भर करते हैं तो "भाषा निर्भरता" अंतर्निहित पैरामीटर गुजरने का एक अपमानजनक रूप है। –

1

एक सरल समाधान अपने

f x = ... 

f2 g x = ... 

के लिए और फिर

f = f2 g 
ftest = f2 gtest 
+1

यह अधिक "मजेदार" हो जाता है जब जी को बार-बार कॉल करने में सक्षम होने की उम्मीद है, लेकिन यह दोनों और कक्षा-आधारित समाधान में हल करने योग्य है। – ephemient

+0

यह समझ में आता है। मुझे लगता है कि अगर मैं सिर्फ एक सेवा (फ़ंक्शन के कुछ तार्किक समूह) से एक फ़ंक्शन का उपयोग कर रहा हूं, तो फ़ंक्शन को पैरामीटर के रूप में पास करना सबसे आसान है। लेकिन अगर मैं कई कार्यों का उपयोग कर रहा हूं, तो कक्षा बनाने से पैरामीटर की संख्या कम हो जाएगी। –

3

तुम सिर्फ f को g नाम के एक समारोह को पारित नहीं किया जा सका बदलने के लिए हो सकता है? जब तक g इंटरफ़ेस typeOfSomeParms -> gReturnType को पूरा करता है, तो आपको वास्तविक फ़ंक्शन या नकली फ़ंक्शन में पास करने में सक्षम होना चाहिए।

जैसे

f g = do 
    ... 
    g someParams 
    ... 

मैं जावा में अपने आप निर्भरता इंजेक्शन का उपयोग नहीं किया है, लेकिन ग्रंथों मैं पढ़ लिया है यह उच्च क्रम कार्यों गुजर तरह एक बहुत ध्वनि है, तो हो सकता है कि आप यही चाहते हो जाएगा बनाया है।


रिस्पांस संपादित करने के लिए: ephemient का जवाब बेहतर है अगर आप एक enterprisey तरह से समस्या को हल करने की जरूरत है, क्योंकि आप एक प्रकार से अधिक फ़ंक्शन युक्त परिभाषित करते हैं। मेरे द्वारा प्रस्तावित प्रोटोटाइप तरीका केवल एक प्रकार के प्रकार को परिभाषित किए बिना कार्यों का एक गुच्छा पास करेगा। लेकिन फिर मैं शायद ही कभी टाइप एनोटेशन लिखता हूं, इसलिए रीफैक्टरिंग करना बहुत मुश्किल नहीं है।

+1

क्या "एंटरप्राइसी" तारीफ माना जाता है? ;) – ephemient

0

आप अलग-अलग नामों के साथ अपने दो फ़ंक्शन कार्यान्वयन कर सकते हैं, और g एक चर होगा जो या तो आपको एक या दूसरे की आवश्यकता के रूप में परिभाषित किया जाता है।

g :: typeOfSomeParms -> gReturnType 
g = g_mock -- change this to "g_real" when you need to 

g_mock someParms = ... -- mock implementation of g 

g_real someParms = ... -- real implementation of g 
+1

इस दृष्टिकोण का नकारात्मक पक्ष यह है कि मुझे अपने स्रोत कोड को g_mock और g_real के बीच टॉगल जी में बदलने के लिए हर बार जब मैं अपना परीक्षण या मेरा असली उत्पाद चलाता हूं। –

15

एक अन्य विकल्प:

{-# LANGUAGE FlexibleContexts, RankNTypes #-} 

import Control.Monad.RWS 

data (Monad m) => ServiceImplementation m = ServiceImplementation 
    { serviceHello :: m() 
    , serviceGetLine :: m String 
    , servicePutLine :: String -> m() 
    } 

serviceHelloBase :: (Monad m) => ServiceImplementation m -> m() 
serviceHelloBase impl = do 
    name <- serviceGetLine impl 
    servicePutLine impl $ "Hello, " ++ name 

realImpl :: ServiceImplementation IO 
realImpl = ServiceImplementation 
    { serviceHello = serviceHelloBase realImpl 
    , serviceGetLine = getLine 
    , servicePutLine = putStrLn 
    } 

mockImpl :: (Monad m, MonadReader String m, MonadWriter String m) => 
    ServiceImplementation m 
mockImpl = ServiceImplementation 
    { serviceHello = serviceHelloBase mockImpl 
    , serviceGetLine = ask 
    , servicePutLine = tell 
    } 

main = serviceHello realImpl 
test = case runRWS (serviceHello mockImpl) "Dave"() of 
    (_, _, "Hello, Dave") -> True; _ -> False 

यह वास्तव में हास्केल में OO स्टाइल कोड बनाने के लिए कई मायनों में से एक है।

+0

यह वास्तव में अच्छा है - आईओ() में मुख्य रन लेकिन परीक्षण एक शुद्ध कार्य है। कंपाइलर एक्सटेंशन मुझे थोड़ा डराता है, और Control.Monad.RWS क्या है? Google बहुत उपयोगी नहीं है। ऐसा लगता है कि मुझे और सीखना है ... – minimalis

+0

@minimalis 'Control.Monad.RWS' 'रीडर', 'राइटर', और' स्टेट 'मोनैड क्लास को परिभाषित करता है। – ephemient

+0

धन्यवाद, यह अब समझ में आता है। ऐसा लगता है कि यदि आप mockImpl के प्रकार को mockImpl :: ServiceImplementation (RWS स्ट्रिंग स्ट्रिंग()) ' – minimalis

5

कई कार्यों के बारे में पूछने के संपादन पर अनुवर्ती करने के लिए, एक विकल्प केवल उन्हें रिकॉर्ड प्रकार में रखना और रिकॉर्ड पास करना है। फिर आप रिकॉर्ड प्रकार को अपडेट करके नए जोड़ सकते हैं। उदाहरण के लिए:

data FunctionGroup t = FunctionGroup { g :: Int -> Int, h :: t -> Int } 

a grp ... = ... g grp someThing ... h grp someThingElse ... 

कुछ अन्य मामलों में व्यवहार्य हो सकता है कि एक और विकल्प टाइप वर्गों का उपयोग करना है। उदाहरण के लिए:

class HasFunctionGroup t where 
    g :: Int -> t 
    h :: t -> Int 

a :: HasFunctionGroup t => <some type involving t> 
a ... = ... g someThing ... h someThingElse 

यह केवल काम करता है (यदि आप बहु पैरामीटर प्रकार कक्षाओं का उपयोग या अनेक प्रकार) है कि कार्यों में क्या समानता है आप एक प्रकार मिल सकता है, लेकिन मामलों में यह उचित है, जहां यह आप दे देंगे अच्छा idiomatic Haskell।

+0

आप कौन सा पसंद करेंगे? – David

+0

यदि इसे जोड़ने के लिए प्राकृतिक प्रकार है, तो मैं टाइप क्लास पसंद करूंगा। अन्यथा, शायद रिकॉर्ड। –

1

यदि आप जिन फ़ंक्शंस पर निर्भर हैं वे किसी अन्य मॉड्यूल में हैं तो आप दृश्य मॉड्यूल कॉन्फ़िगरेशन के साथ गेम खेल सकते हैं ताकि वास्तविक मॉड्यूल या नकली मॉड्यूल आयात किया जा सके।

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

बेशक यह मानता है कि आप monadic मानों के साथ काम नहीं कर रहे हैं, इसलिए इससे कोई फर्क नहीं पड़ता कि क्या कहा जाता है या किस पैरामीटर के साथ।उस स्थिति में आपको शायद यह प्रदर्शित करने की ज़रूरत है कि सही साइड इफेक्ट सही समय पर लागू किए जा रहे हैं, इसलिए जब आवश्यक हो तो निगरानी की जा रही है।

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

संबंधित मुद्दे