2012-04-05 9 views
7

私はmethod1,method2,method3のような機能を持っています。それらのすべてのために、testMethod1,testMethod2,testMethod3のようなHUnitのテスト機能があります。内部に関数名を取得

私はエラー メッセージの接頭辞として機能名の冗長コピーを避けるために、そのような何かそれを呼び出すしたいと思います
testMethod1 = TestCase $ 
    assertEqual "testmethod1" ... 

testMethod2 = TestCase $ 
    assertEqual "testmethod2" ... 

testMethod3 = TestCase $ 
    assertEqual "testmethod3" ... 

:それは(任意の「魔法」のトリックが評価されて達成することができますどのように

testMethod1 = TestCase $ 
    assertEqual_ ... 

)?

実際に質問すると、関数名をその定義の中でどのように使うことができるのでしょうか?


更新

それは私があまりにも事態のタイプを処理したいことを、実際には元の質問から明らかではありません。

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

は最後に、私はそのような何かを書きたい:

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

あなたが行うことはできませんこれは直接(つまり、あなたのテストケースが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_のすべての発生を置き換えることです。 Template HaskellのScrap Your Boilerplateサポートのおかげで、AST全体をトラバースする必要はなく、Expノードごとに変換を指定するだけです。

testCaseに渡される前に、引用された式が型チェックされているため、assertEqual_は正しい型のバインドされた識別子でなければならないことに注意してください。さらに、testCaseは、GHCの段階制限のために、使用されているモジュールとは別のモジュールで定義する必要があります。

+1

これはリフレクションがサポートされていないことを意味しますか? –

+1

@Riccardo:内部から関数の名前に簡単にアクセスできるようにすることは、むしろ不純です。メタプログラミングで知られている言語でもあるLispでも、マクロ機能(この場合はTemplate Haskell)を使用することが解決策です。 – ehird

+1

私の質問を更新してください。 –

1

既存の答えは、メタプログラミングでこれを行う方法を説明していますが、への1つの方法は、を避けるために、名前を引数として持つ匿名テストを行うことです。

私たちはその後、自分の名前(この場合には、私はちょうど生のアサーションを使用しています、プラス map-syntaxパッケージからいくつかのシンタックスシュガー)とそれらを関連付けるために Data.Mapを使用することができます

:私たちは、これらを実行するには

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 

  • アサーション待機-FOR-に名前を渡し
  • 引数として名前と主張-待ちのため-名をとり:機能を使用してData.Mapを折ることができます

    runTests = foldWithKey go (return()) tests 
        where go name test = (runTestTT (TestCase (test name)) >>) 
    
    :名
  • TestCase
  • に結果AssertionTestCase
  • は、私たちは、デフォルトモナドアクションとしてreturn()を使用>>

を使用して、別のモナドのアクションにバインドし実行渡し

この結果は次のようになります。

> 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 
関連する問題