8

method1次のような関数がたくさんありmethod2ますmethod3。それらすべてに対して、 、、 のHUnitようなテスト関数があります。testMethod1testMethod2testMethod3

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
4

2 に答える 2

11

これを直接行うことはできません (つまり、テスト ケースが で始まるようにする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_し、引用符で囲まれた式内の変数のすべての出現箇所を に置き換えることです。Template Haskell のScrap Your Boilerplateサポートのおかげで、AST 全体をトラバースする必要はなく、ノードごとに変換を指定するだけです。assertEqual lowerNameExp

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

于 2012-04-05T22:05:28.017 に答える
2

既存の回答では、メタプログラミングでこれを行う方法が説明されていますが、問題を回避する 1 つの方法は、名前を引数として取る匿名のテストを使用することです。

次に、 a を使用し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の関数を使用して を折りたたむことができます。

  • name と assertion-waiting-for-a-name を引数として取ります
  • assertion-waiting-for-a-name に名前を渡します
  • 結果Assertionをに渡しますTestCase
  • を実行しますTestCase
  • を使用して、別のモナド アクションにバインドします。>>

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
于 2014-10-03T12:25:59.877 に答える