2012-04-05 22 views
7

मेरे पास फ़ंक्शन का एक गुच्छा है: method1, method2, method3। उनमें से सभी के लिए HUnit परीक्षण कार्य हैं: testMethod1, testMethod2, testMethod3इसके अंदर फ़ंक्शन नाम प्राप्त करें

testMethod1 = TestCase $ 
    assertEqual "testmethod1" ... 

testMethod2 = TestCase $ 
    assertEqual "testmethod2" ... 

testMethod3 = TestCase $ 
    assertEqual "testmethod3" ... 

मैं त्रुटि संदेश का उपसर्ग के रूप में समारोह के नाम का अनावश्यक नकल से बचने और यह ऐसा ही कुछ कॉल करना चाहते हैं:

testMethod1 = TestCase $ 
    assertEqual_ ... 

यह कैसे प्राप्त किया जा सकता है (किसी भी "जादू" चाल की सराहना की)?

तो वास्तव में सवाल यह है कि इसकी परिभाषा के अंदर कार्य नाम कैसे लिया जा सकता है?


अद्यतन

यह वास्तव में मूल प्रश्न से स्पष्ट नहीं है, कि मैं भी स्थिति में उस प्रकार संभाल करना चाहते हैं:

tProcess = TestCase $ do 
    assertEqual_ testResult $ someTest 
    assertEqual_ anotherTestResult $ anotherTest 
    assertEqual_ resultAgain $ testAgain 
+1

टेम्पलेट Haskell – pat

+2

मेरा पुराना सवाल उपयोगी हो सकता है: http://stackoverflow.com/questions/7896928/how-to-get-variable-name-in-haskell –

उत्तर

10

आप ऐसा नहीं कर सकते:

tProcess = TestCase $ do 
    assertEqual "tProcess" testResult $ someTest 
    assertEqual "tProcess" anotherTestResult $ anotherTest 
    assertEqual "tProcess" resultAgain $ testAgain 

अंत में मैं ऐसा ही कुछ लिखना चाहते हैं यह सीधे (यानी कि आपका परीक्षण केस testMethodN = ... से शुरू होता है), लेकिन आप इसे प्राप्त करने के लिए Template Haskell का उपयोग कर सकते हैं:

testCase "testMethod1" [| do 
    assertEqual_ a b 
    assertEqual_ c d 
|] 

इसमें testCase :: String -> Q Exp -> Q [Dec] लिखना शामिल है, परीक्षण के नाम को बदलने और घोषणाओं की सूची में उद्धृत अभिव्यक्ति को बदलने के लिए एक फ़ंक्शन शामिल है। उदाहरण के लिए:

{-# LANGUAGE TemplateHaskell #-} 
     
import Data.Char 
import Control.Applicative 
import Control.Monad 
import Language.Haskell.TH 
import Data.Generics 

assertEqual :: (Eq a) => String -> a -> a -> IO() 
assertEqual s a b = when (a /= b) . putStrLn $ "Test " ++ s ++ " failed!" 

assertEqual_ :: (Eq a) => a -> a -> IO() 
assertEqual_ = error "assertEqual_ used outside of testCase" 

testCase :: String -> Q Exp -> Q [Dec] 
testCase name expr = do 
    let lowerName = map toLower name 
    e' <- [| assertEqual lowerName |] 
    pure <$> valD 
        (varP (mkName name)) 
        (normalB (everywhere (mkT (replaceAssertEqual_ e')) <$> expr)) 
        [] 
  where 
    replaceAssertEqual_ e' (VarE n) | n == 'assertEqual_ = e' 
    replaceAssertEqual_ _ e = e 

यहाँ मूल विचार का नाम दिया की एक परिभाषा उत्पन्न, और assertEqual lowerName के साथ उद्धृत अभिव्यक्ति में चर assertEqual_ के हर घटना को बदलने के लिए है। टेम्पलेट हास्केल के Scrap Your Boilerplate समर्थन के लिए धन्यवाद, हमें पूरे एएसटी को पार करने की आवश्यकता नहीं है, बस प्रत्येक Exp नोड के लिए एक रूपांतरण निर्दिष्ट करें।

ध्यान दें कि assertEqual_ सही प्रकार के साथ एक बाध्य पहचानकर्ता होना चाहिए, क्योंकि उद्धृत अभिव्यक्ति को testCase पर पारित होने से पहले टाइप चेक किया गया है। इसके अतिरिक्त, testCase को जीएचसी के मंच प्रतिबंध के कारण, एक अलग मॉड्यूल में परिभाषित किया जाना चाहिए।

+1

इसका मतलब है कि प्रतिबिंब समर्थित नहीं है? –

+1

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

+1

मेरा प्रश्न अपडेट करें। –

1

मौजूदा उत्तरों मेटाप्रोग्रामिंग के साथ ऐसा करने के तरीके को समझाते हैं, लेकिन से बचने का एक तरीका यह है कि समस्या का नाम अज्ञात परीक्षण है जो उनका नाम तर्क के रूप में लेता है।

हम तो एक Data.Map उपयोग कर सकते हैं उनके नाम के साथ उन्हें संबद्ध करने के लिए (इस मामले मैं सिर्फ कच्चे दावे का उपयोग कर रहा है, साथ ही map-syntax पैकेज से कुछ वाक्यात्मक चीनी में):

import Data.Map 
import Data.Map.Syntax 
import Test.HUnit 

assertEqual_ x y n = assertEqual n x y 

Right tests = runMap $ do 
    "test1" ## assertEqual_ 1 2 
    "test2" ## assertEqual_ 1 1 
    "test3" ## assertEqual_ 3 2 

इन चलाने के लिए, हम

  • नाम और जोर-प्रतीक्षा के लिए एक नाम तर्कों के रूप लेता है
  • जोर-वेटिंग-तक- के नाम पासेस: एक समारोह जो का उपयोग कर Data.Map फोल्ड कर सकते हैं एक-नाम
  • जिसके परिणामस्वरूप AssertionTestCase को
  • चलाता TestCase
  • एक और monadic कार्रवाई को बांधता है गुजरता है, का उपयोग कर >>

हम अपने डिफ़ॉल्ट monadic कार्रवाई के रूप में return() का उपयोग करें:

runTests = foldWithKey go (return()) tests 
    where go name test = (runTestTT (TestCase (test name)) >>) 

यह परिणाम देता है जैसे:

> go 
### Failure: 
test1 
expected: 1 
but got: 2 
Cases: 1 Tried: 1 Errors: 0 Failures: 1 
Cases: 1 Tried: 1 Errors: 0 Failures: 0 
### Failure: 
test3 
expected: 3 
but got: 2 
Cases: 1 Tried: 1 Errors: 0 Failures: 1 
संबंधित मुद्दे