我有一个玩具程序:
$ cat a.hs
main = putStrLn "Toy example"
$ runghc a.hs
Toy example
让我们添加一些Template Haskell:
$ cat b.hs
{-# LANGUAGE TemplateHaskell #-}
id [d|
main = putStrLn "Toy example"
|]
$ runghc b.hs
b.hs:3:0: parse error (possibly incorrect indentation)
然后,让我们修复缩进:
$ cat c.hs
{-# LANGUAGE TemplateHaskell #-}
id [d|
main = putStrLn "Toy example"
|]
$ runghc c.hs
Toy example
单个空格就足够了,但我必须缩进两个尾随行。
我可以避免缩进大部分模块吗? (我的真实模块只有一行代码。)(并且没有使用{ ; ; }
符号?)
我确实希望所有模块声明都在引号中捕获 - 在正常代码中我可以用(...)
替换$ ...
,是否有一些等同于{{ 1}}这会让我避开括号和缩进吗?
或者模块 A 是否有某种方式可以说导入 A 的任何模块 B 的顶级声明都是自动的由函数 A 导出处理?
注意:
[d|...|]
更复杂 - 它会扫描启动id
的变量名称的声明,并构建包含它们的测试套件。有没有其他纯Haskell方式我可以这样做而不直接修改源文件?prop_
- 但行为在其他方面是相同的。答案 0 :(得分:4)
如果测试套件适用于QuickCheck,我建议您使用新的All
模块:
http://hackage.haskell.org/packages/archive/QuickCheck/2.4.1.1/doc/html/Test-QuickCheck-All.html
它做了同样的事情,除了它通过访问文件系统并解析splice所在的文件来获取属性的名称(如果你使用的是其他测试框架,你仍然可以使用相同的方法)。
如果你真的想引用整个文件,你可以使用准引号(不需要缩进)。你可以在haskell-src-meta上轻松构建你的引用,但我建议不要使用这种方法,因为它不支持一些Haskell功能,它可能会给出错误的错误消息。
聚合测试套装是一个难题,人们可能会扩展名称收集例程以某种方式跟随导入,但这是很多工作。这是一个解决方法:
您可以使用forAllProperties
的此修改版本:
import Test.QuickCheck
import Test.QuickCheck.All
import Language.Haskell.TH
import Data.Char
import Data.List
import Control.Monad
allProperties :: Q Exp -- :: [(String,Property)]
allProperties = do
Loc { loc_filename = filename } <- location
when (filename == "<interactive>") $ error "don't run this interactively"
ls <- runIO (fmap lines (readFile filename))
let prefixes = map (takeWhile (\c -> isAlphaNum c || c == '_') . dropWhile (\c -> isSpace c || c == '>')) ls
idents = nubBy (\x y -> snd x == snd y) (filter (("prop_" `isPrefixOf`) . snd) (zip [1..] prefixes))
quickCheckOne :: (Int, String) -> Q [Exp]
quickCheckOne (l, x) = do
exists <- return False `recover` (reify (mkName x) >> return True)
if exists then sequence [ [| ($(stringE $ x ++ " on " ++ filename ++ ":" ++ show l),
property $(mono (mkName x))) |] ]
else return []
[|$(fmap (ListE . concat) (mapM quickCheckOne idents)) |]
您还需要不从All导出的函数runQuickCheckAll
:
runQuickCheckAll :: [(String, Property)] -> (Property -> IO Result) -> IO Bool
runQuickCheckAll ps qc =
fmap and . forM ps $ \(xs, p) -> do
putStrLn $ "=== " ++ xs ++ " ==="
r <- qc p
return $ case r of
Success { } -> True
Failure { } -> False
NoExpectedFailure { } -> False
在每个测试模块中,您现在定义
propsN = $allProperties
其中N
是某个数字或其他唯一标识符(或者您可以使用相同的名称并在下面的步骤中使用限定名称)。
在主要测试套件中定义
props :: [(String,Property)]
props = concat [props1, props2 ... propsN]
如果你真的想避免为每个模块添加一个列表成员,你可以制作一个生成这个列表的TH脚本。
要运行所有测试,只需说
即可runTests = runQuickCheckAll quickCheckResult props
答案 1 :(得分:3)
[我的程序]扫描启动prop_的变量名称的声明,并构建包含它们的测试套件。有没有其他纯Haskell方式我可以这样做,而不直接修改源文件?
是的,有!使用the language-haskell-extract
package。
{-# LANGUAGE TemplateHaskell #-}
import Language.Haskell.Extract
import Test.QuickCheck
prop_foo xs = reverse (reverse xs) == (xs :: [Int])
prop_bar = 2 + 2 == 4
properties = $(functionExtractorMap "^prop_"
[|\name prop -> putStrLn name >> quickCheck prop|])
main = sequence_ properties
运行这个,我们得到:
prop_foo
+++ OK, passed 100 tests.
prop_bar
+++ OK, passed 100 tests.
然而,在你重新发明轮子之前,我还建议你看看the test-framework-th
package,这几乎可以做到这一点,但也支持HUnit并且有一个很好的测试跑步者(带颜色!)。< / p>
{-# LANGUAGE TemplateHaskell #-}
import Test.Framework.Providers.HUnit
import Test.Framework.Providers.QuickCheck2
import Test.Framework.TH
import Test.HUnit
import Test.QuickCheck
prop_bar = 1+1 == 2
case_foo = 2+2 @?= 4
main = $(defaultMainGenerator)
输出:
Main:
bar: [OK, passed 100 tests]
foo: [OK]
Properties Test Cases Total
Passed 1 1 2
Failed 0 0 0
Total 1 1 2
如果你想组合来自多个文件的测试,还有一个testGroupGenerator
非常有用。