2014-09-01 8 views
7

का परीक्षण कानून मैं एपीआई के माध्यम से वेब सेवा तक पहुंचने के लिए एक लाइब्रेरी लिख रहा हूं। मैं एपीआई कार्रवाईसाइड-इफेक्टिंग मोनड

case class ApiAction[A](run: Credentials => Either[Error, A]) 

प्रतिनिधित्व करने के लिए साधारण क्लास परिभाषित किया है और कुछ कार्यों कि वेब सेवा करता कॉल

// Retrieve foo by id 
def get(id: Long): ApiAction[Foo] = ??? 

// List all foo's 
def list: ApiAction[Seq[Foo]] = ??? 

// Create a new foo 
def create(name: String): ApiAction[Foo] = ??? 

// Update foo 
def update(updated: Foo): ApiAction[Foo] = ??? 

// Delete foo 
def delete(id: Long): ApiAction[Unit] = ??? 

मैं भी ApiAction एक इकाई द्वारा किए गए

implicit val monad = new Monad[ApiAction] { ... } 

तो मैं कर सकता

create("My foo").run(c) 
get(42).map(changeFooSomehow).flatMap(update).run(c) 
get(42).map(_.id).flatMap(delete).run(c) 
जैसे कुछ करें

अब मैं मुसीबतों अपनी इकाई कानूनों का परीक्षण

val x = 42 
val unitX: ApiAction[Int] = Monad[ApiAction].point(x) 

"ApiAction" should "satisfy identity law" in { 
    Monad[ApiAction].monadLaw.rightIdentity(unitX) should be (true) 
} 

क्योंकि monadLaw.rightIdentityequal

def rightIdentity[A](a: F[A])(implicit FA: Equal[F[A]]): Boolean = 
    FA.equal(bind(a)(point(_: A)), a) 

का उपयोग करता है और वहाँ कोई Equal[ApiAction] है।

[error] could not find implicit value for parameter FA: scalaz.Equal[ApiAction[Int]] 
[error]  Monad[ApiAction].monadLaw.rightIdentity(unitX) should be (true) 
[error]           ^

समस्या मैं कल्पना भी नहीं कर सकते हैं कि यह कैसे Equal[ApiAction] को परिभाषित करना संभव हो सकता है। ApiAction एक कार्य अनिवार्य है, और मुझे कार्यों पर किसी भी समानता संबंध के बारे में पता नहीं है। बेशक ApiAction चलाने के परिणामों की तुलना करना संभव है, लेकिन यह वही नहीं है।

मुझे लगता है कि मैं कुछ गलत कर रहा हूं या कुछ आवश्यक समझ नहीं पा रहा हूं। तो मेरे प्रश्न हैं:

  • क्या यह ApiAction के लिए एक मोनड होने के लिए समझ में आता है?
  • क्या मैंने ApiAction सही बनाया है?
  • मुझे अपने मोनैड कानूनों का परीक्षण कैसे करना चाहिए?
+0

अनंत डोमेन वाले कार्यों के लिए कोई गणना योग्य समानता संबंध नहीं है। आप एक सीलबंद विशेषता के एक अमूर्त विधि को 'रन' बना सकते हैं, और फिर अपने 'अपियाक्शन' को व्युत्पन्न केस क्लास और केस ऑब्जेक्ट्स के रूप में कार्यान्वित कर सकते हैं। –

उत्तर

4

मैं आसान लोगों से शुरू करूंगा: हां, यह ApiAction के लिए एक मोनड होने के लिए समझ में आता है। और हाँ, आपने इसे उचित तरीके से डिज़ाइन किया है - यह डिज़ाइन हास्केल में IO मोनैड जैसा दिखता है।

मुश्किल सवाल यह है कि आपको इसका परीक्षण कैसे करना चाहिए।

एकमात्र समानता संबंध जो समझ में आता है वह "समान इनपुट के समान उत्पादन उत्पन्न करता है", लेकिन यह केवल कागज पर वास्तव में उपयोगी है, क्योंकि कंप्यूटर के सत्यापन के लिए यह संभव नहीं है, और यह शुद्ध कार्यों के लिए केवल सार्थक है। दरअसल, हास्केल का IO मोनैड, जिसमें आपके मोनैड की कुछ समानताएं हैं, Eq लागू नहीं करती हैं। तो यदि आप Equal[ApiAction] लागू नहीं करते हैं तो शायद आप सुरक्षित जमीन पर हैं।

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

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

+0

महान जवाब! आप इतने सही हैं कि समानता केवल शुद्ध कार्यों के लिए समझ में आती है, 'apiAction.run == apiAction.run' साइड इफेक्ट्स के कारण सामान्य रूप से नहीं होती है, और पेंसिल-एंड-पेपर का उपयोग करके कानून साबित करने के लिए हमें लगता है कि कार्रवाई कभी विफल नहीं रहता। – lambdas

0

मुझे लगता है कि आप इसे अपने वर्ग को एक कक्षा में लपेटने के लिए मैक्रो या प्रतिबिंब का उपयोग करने के नकारात्मक पक्ष के साथ कार्यान्वित कर सकते हैं जिसमें एएसटी शामिल है। फिर आप अपने एएसटी की तुलना करके दो कार्यों की तुलना कर सकते हैं।

What's the easiest way to use reify (get an AST of) an expression in Scala?

+0

मुझे नहीं लगता कि यह एक अच्छा समाधान है। मैं बस एक कार्यात्मक तरीके से पुस्तकालय डिजाइन करने की कोशिश कर रहा हूं और सोच रहा हूं कि मैं इसे गलत कर रहा हूं। वैसे भी धन्यवाद। अपने उत्तर से संबंधित - एक समान तरीके से समान व्यवहार वाले कार्यों को कार्यान्वित करना संभव है;) – lambdas

+0

सच है, लेकिन इसका मतलब यह होगा कि आपका कोड गीला होगा। – samthebest

1

देखें यह lambdas FunctionN, जहाँ आप केवल उदाहरण समानता है की अनाम उपवर्गों किया जा रहा करने पर निर्भर करता है, तो केवल एक ही Anonymus उपवर्ग के साथ ही बराबर है।

आप इसे कैसे कर सकता है में से एक विचार: अपने संचालन के बजाय उदाहरणों Function1 की ठोस उपवर्गों करें:

abstract class ApiAction[A] extends (Credentials => Either[Error, A]) 
// (which is the same as) 
abstract class ApiAction[A] extends Function1[Credentials, Either[Error, A]] 

कौन सा उदाहरण बनाने के लिए मामला अपने मामलों के लिए वस्तुओं के लिए आप की अनुमति होगी

case class get(id: Long) extends ApiAction[Foo] { 
    def apply(creds: Credentials): Either[Error, Foo] = ... 
} 

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

val a = get(1) 
val b = get(1) 
a == b 

तुम भी मैं की तरह Function1 के विस्तार के बिना ऐसा कर सकता है किया था और जैसे तुमने किया था एक रन फ़ील्ड का उपयोग, लेकिन इस तरह से सबसे दिया succint उदाहरण कोड।

+0

फिर 'अपियाक्शन' पर मोनैड को परिभाषित करना असंभव है क्योंकि 'बिंदु' अपरिभाषित है (आप एक अमूर्त वर्ग के उदाहरण नहीं बना सकते हैं)। – lambdas

+0

लेकिन आप इसके लिए पहचान/बिंदु बना सकते हैं? केस क्लास प्वाइंट (ए: ए) अपियाक्शन बढ़ाता है [ए] {डीई लागू (क्रेडिट: प्रमाण पत्र): या तो [त्रुटि, फू] = दाएं (ए)} – johanandren

+0

मैंने इसके बारे में नहीं सोचा, तुम सही हो! – lambdas

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