2012-03-13 21 views
14

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

gen :: a -> ([a] -> [a]) -> ([a] -> Bool) -> a 

विचार यह है कि यह एक उदाहरण जनरेटर है। मैं एक a से शुरू करने जा रहा हूं, [a] की सिंगलटन सूची बनाएं, फिर [a] की नई सूचियां बनाएं जब तक कोई भविष्य मुझे रोकने के लिए कहता है। एक कॉल इस प्रकार दिखाई देंगे:

gen init next stop 

जहां

init :: a 
next :: [a] -> [a] 
stop :: [a] -> Bool 

यहाँ संपत्ति है मैं परीक्षण करना चाहते हैं:

gen init next stop के लिए किसी भी कॉल पर, gen वादों एक पारित करने के लिए कभी नहीं next पर खाली सूची।

मैं इस संपत्ति QuickCheck मदद से इसका परीक्षण कर सकते हैं, और यदि ऐसा है तो, तो कैसे?

+5

यहां एक संबंधित संपत्ति है: "किसी भी गैर-खाली इनपुट के लिए, 'अगला' एक गैर-खाली आउटपुट उत्पन्न करेगा"। आप इसके बजाय, या आपके द्वारा उल्लेख की जाने वाली संपत्ति के अतिरिक्त परीक्षण करने में रुचि रखते हैं। –

+1

@ जॉनल वास्तव में ऐसा! लेकिन यह 'अगली' की संपत्ति है, न कि 'जीन', और 'अगली' पहली ऑर्डर है, इसलिए मुझे पता है कि इसका परीक्षण कैसे किया जाए। –

उत्तर

10

अगर आप gen के कार्यान्वयन दिया यह मदद मिलेगी, वहीं मैं अनुमान लगा कि यह कुछ इस तरह चला जाता है हूँ:

gen :: a -> ([a] -> [a]) -> ([a] -> Bool) -> a 
gen init next stop = loop [init] 
    where 
    loop xs | stop xs = head xs 
      | otherwise = loop (next xs) 

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

genWitness :: a -> ([a] -> [a]) -> ([a] -> Bool) -> (a,[[a]]) 
genWitness init next stop = loop [init] 
    where 
    loop xs | stop xs = (head xs,[xs]) 
      | otherwise = second (xs:) (loop (next xs)) 

हम Control.Arrow से second का उपयोग करें। मूल gen आसानी से genWitness:

gen' :: a -> ([a] -> [a]) -> ([a] -> Bool) -> a 
gen' init next stop = fst (genWitness init next stop) 

आलसी मूल्यांकन के लिए धन्यवाद संदर्भ में परिभाषित किया गया है यह हमें बहुत भूमि के ऊपर नहीं देंगे। संपत्ति पर वापस! क्विक चेक, से जेनरेट किए गए फ़ंक्शंस को प्रदर्शित करने के लिए हम मॉड्यूल Test.QuickCheck.Function का उपयोग करते हैं। हालांकि यह सख्ती से जरूरी नहीं है, संपत्ति की मोनोमोर्फिस की अच्छी आदत है: हम को मॉनिमोर्फिज्म प्रतिबंध को यूनिट सूचियों में बनाने की अनुमति देने के बजाय Int एस की सूचियों का उपयोग करते हैं।

prop_gen :: Int -> (Fun [Int] [Int]) -> (Fun [Int] Bool) -> Bool 
prop_gen init (Fun _ next) (Fun _ stop) = 
    let trace = snd (genWitness init next stop) 
    in all (not . null) trace 

हमें कोशिश QuickCheck के साथ चल रहा है:: हमें अब राज्य संपत्ति चलो

ghci> quickCheck prop_gen 

कुछ पाश करने लगता है ... हाँ निश्चित रूप से: सूची में gen छोरों अगर stopnext से True कभी नहीं है!

prop_gen_prefix :: Int -> (Fun [Int] [Int]) -> (Fun [Int] Bool) -> Int -> Bool 
prop_gen_prefix init (Fun _ next) (Fun _ stop) prefix_length = 
    let trace = snd (genWitness init next stop) 
    in all (not . null) (take prefix_length trace) 

अब हम जल्दी से आ जवाबी उदाहरण मिल:

385 
{_->[]} 
{_->False} 
2 

दूसरा समारोह तर्क next है, और अगर यह हमें बजाय इनपुट का पता लगाने बजाय परिमित उपसर्गों को देखने के लिए कोशिश करते हैं खाली सूची, लौटाता है तो gen में लूप next एक खाली सूची देगा।

मुझे आशा है कि यह इस प्रश्न का उत्तर देगा और यह आपको क्विक चेक के साथ उच्च-आदेश कार्यों का परीक्षण करने के तरीके में कुछ अंतर्दृष्टि देता है।

+2

यदि आपने 'gen' से 'gen :: monad m => a -> ([a] -> m [a]) -> ([a] -> m bool) -> ma' में बदल दिया है, तो आप डाल सकते हैं आपका अगला कोड 'अगली' (और 'स्टॉप') के अंदर कोड, और इस तरह के लॉगिंग के साथ कार्यान्वयन के बारे में चिंता न करें। – rampion

+0

वाह। मैं 'जीन' के चारों ओर किसी प्रकार का रैपर लगाने की उम्मीद कर रहा था, लेकिन मुझे यकीन नहीं है कि मैं इस अतिरिक्त गंदगी के साथ अपने अच्छे साफ 'जीन' को अपनाना चाहता हूं। मैं इसे वापस देखूंगा। –

+0

@ नॉर्मन रैमसे: इसे रैंपियन के सुझाव का उपयोग करके इसे सिर्फ मोनैडिक करके और फिर एक ट्रेस करने के लिए लेखक मोनड का उपयोग करने के लिए बहुत अधिक जंक की आवश्यकता नहीं होती है। फिर 'अगली' को केवल लेखक मोनड में अपना इनपुट लिखना होगा। – danr

4

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

import Test.QuickCheck 
import Test.QuickCheck.Function 
import Control.DeepSeq 

prop_gen :: Int -> (Fun [Int] [Int]) -> (Fun [Int] Bool) -> Bool 
prop_gen x (Fun _ next) (Fun _ stop) = gen x next' stop `deepseq` True 
    where next' [] = undefined 
     next' xs = next xs 

इस तकनीक gen को संशोधित करने की आवश्यकता नहीं है।

+0

इसमें कोई समस्या है: यदि 'स्टॉप' फ़ंक्शन कभी भी 'True' नहीं लौटाता है, तो यह हमेशा के लिए लूप होगा। एक क्विक चेक प्रदान किए गए फ़ंक्शन के बजाय, उपयोगकर्ता को 'स्टॉप' बनाने की आवश्यकता होती है जिसे किसी बिंदु पर 'True' वापस करने की गारंटी दी जाती है। 'कॉन्स ट्रू' जैसे फ़ंक्शन का छोटा, यह गारंटी देना मुश्किल हो सकता है। –

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