我有很多功能,例如: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 "tProcess" testResult $ someTest
assertEqual "tProcess" anotherTestResult $ anotherTest
assertEqual "tProcess" resultAgain $ testAgain
最后我想写一些类似的东西:
tProcess = TestCase $ do
assertEqual_ testResult $ someTest
assertEqual_ anotherTestResult $ anotherTest
assertEqual_ resultAgain $ testAgain
答案 0 :(得分: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_
替换引用表达式中变量assertEqual lowerName
的每个出现位置。感谢Template Haskell的Scrap Your Boilerplate支持,我们不需要遍历整个AST,只需为每个Exp
节点指定转换。
请注意,assertEqual_
必须是具有正确类型的绑定标识符,因为引用的表达式在传递给testCase
之前已经过类型检查。此外,由于GHC的阶段限制,testCase
必须在一个单独的模块中定义,而不是它所使用的模块。
答案 1 :(得分:2)
现有的答案解释了如何使用元编程实现这一点,但避免问题的一种方法是进行匿名测试,将其名称作为参数。
然后我们可以使用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
:
Assertion
传递给TestCase
TestCase
>>
我们使用return ()
作为默认的monadic操作:
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