我想在我的库中对各种类型进行一些类似的测试。
为简化起见,假设我有许多实现Num
类的向量类型,并且我想生成相同的QuickCheck属性检查prop_absNorm x y = abs x + abs y >= abs (x+y)
,它可以用于库中的所有类型。 / p>
我使用TH生成这样的属性:
$(writeTests
(\t ->
[d| prop_absNorm :: $(t) -> $(t) -> Bool
prop_absNorm x y = abs x + abs y >= abs (x+y)
|])
)
我生成测试的功能有以下特征:
writeTests :: (TypeQ -> Q [Dec]) -> Q [Dec]
此函数查找我的向量类VectorMath (n::Nat) t
的所有实例(同时,查找Num
的实例)到reify ''VectorMath
,并相应地生成所有prop函数。
-ddump-splices
显示的内容如下:
prop_absNormIntX4 :: Vector 4 Int -> Vector 4 Int -> Bool
prop_absNormIntX4 x y = abs x + abs y >= abs (x+y)
prop_absNormCIntX4 :: Vector 4 CInt -> Vector 4 CInt -> Bool
prop_absNormCIntX4 x y = abs x + abs y >= abs (x+y)
...
prop_absNormFloatX4 :: Vector 4 Float -> Vector 4 Float -> Bool
prop_absNormFloatX4 x y = abs x + abs y >= abs (x+y)
prop_absNormFloatX3 :: Vector 3 Float -> Vector 3 Float -> Bool
prop_absNormFloatX3 x y = abs x + abs y >= abs (x+y)
问题是所有手动编写的属性都被检查,但生成的属性不是。
注1:我在同一个文件中生成了非生成属性(即TH表达式$(..)
与其他道具位于同一文件中)。
注2:用于创建prop函数的类型列表是可变的 - 我想稍后添加VectorMath
的其他实例,因此它们会自动添加到测试列表中。
我认为问题在于HTF(可能也使用TH)会解析原始文件,而不是生成代码的文件 - 但我无法理解为什么会发生这种情况。
所以我的问题是:如何解决这个问题?如果不能使用TH生成的道具,那么可以对各种类型进行QuickCheck测试(即它将它们替换为prop_absNorm :: Vector 4 a -> Vector 4 a -> Bool
)吗?
另一个替代方案可能是使用TH进一步手动将测试条目添加到htf_Main,但我还没有弄清楚如何做到这一点; 它看起来不是一个很好的清洁解决方案。
答案 0 :(得分:3)
如果事先知道生成的属性测试的名称是什么,那么你总是可以手动定义存根,以便HTF看到它们,例如:
$(generate prop test for Int)
$(generate prop test for CInt)
prop_p1 = prop_absNormInt
prop_p2 = prop_absNormCInt
HTF会将测试视为prop_p1
和prop_p2
。您不必在这些存根上添加类型签名。
另一个想法是创建自己的源预处理器来为您添加这些存根(并给它们更好的名称)。您的源预处理器会自动调用htfpp
来完成预处理。
如果您告诉我如何调用TH,我可以向您展示如何编写预处理器。
更新
鉴于你的评论,我会考虑做以下事情:
所以 - 在运行程序以重新生成测试模块之前,测试用例保持不变。
拥有静态测试模块的优势在于您可以确切地确定正在测试的内容。
拥有一个重新创建测试模块的程序,您可以在新的Num实例可用时轻松更新它。
答案 1 :(得分:1)
好的,我设法解决了这个问题。
我们的想法是使用TH来聚合测试并将它们插入到htfMain
中。
除了我在问题中的内容之外,还包括以下步骤:
IO
操作; TestSuite
; htfMain
。为了使用步骤1,我必须使用名为qcAssertion :: (QCAssertion t) => t -> Assertion
的HTF的半内部功能。
此功能可用,但不建议外部使用;它允许很好地运行QuickCheck测试,将它们集成到报告中。
要继续执行第2步,我使用了HTF中的两个函数:makeTestSuite
和makeQuickCheckTest
。
我还使用来自TH的location
函数来提供插入带有测试模板的拼接处的文件名和行(用于更好的测试日志)。
第3步是一个棘手的问题:为此我们需要找到所有生成的测试套件。 问题是TH不允许浏览模块中的所有功能(包括生成的)。 为了解决这个问题,我添加了以下类型类:
class MultitypeTestSuite name where
multitypeTestSuite :: name -> TestSuite
因此,我的函数writeTests
为该数据类型生成新数据类型data MTS[prop_name]
和MultitypeTestSuite
实例。
这允许我稍后在htfMain中使用另一个拼接函数,它将使用reify
从该类的实例生成测试套件列表:
aggregateTests :: ExpQ
aggregateTests = do
ClassI _ instances <- reify ''MultitypeTestSuite
liftM ListE . forM instances
$ \... -> [e| multitypeTestSuite $(...) |]
最后,包括所有生成的测试以及手动编写的测试看起来非常简单:
main :: IO ()
main = htfMain $ htf_importedTests ++ $(aggregateTests)
因此,通过调整函数$(writeTests)
,我现在能够生成和测试参数类型不同的属性 - 适用于同一类型范围内可用的所有类型。
测试结果和日志的包含方式与原始测试相同。
关于问题已经完全解决了。
答案 2 :(得分:1)
HTF不使用TemplateHaskell来收集测试,这会显着减慢编译时间。相反,HTF使用名为htfpp
的自定义预处理器。 htfpp
在编译器之前运行(因此在展开TemplateHaskell拼接之前)。这意味着在使用TemplateHaskell生成测试时,您无法使用htfpp
自动测试发现。
我的建议:无论如何,当你使用TemplateHaskell时,只需使用TemplateHaskell来收集生成的测试用例。此功能未内置于HTF中,但实现此类功能并不困难。这是它:
-- file TH.hs
{-# LANGUAGE TemplateHaskell #-}
module TH ( genTestSuiteFromQcProps ) where
import Language.Haskell.TH
import Test.Framework
import Test.Framework.Location
genTestSuiteFromQcProps :: String -> [Name] -> Q Exp
genTestSuiteFromQcProps suiteName names =
[| makeTestSuite $(stringE suiteName) $(listE genTests) |]
where
genTests :: [ExpQ]
genTests =
map genTest names
genTest :: Name -> Q Exp
genTest name =
[| makeQuickCheckTest $(stringE (show name)) unknownLocation
(qcAssertion $(varE name)) |]
函数genTestSuiteFromQcProps
使用要生成的测试套件的名称和名称列表,引用您的QC属性。 genTestSuiteFromQcProps
返回TestSuite
类型的表达式。 TestSuite是HTF用于组织测试的类型之一。
(htfpp
预处理器在其输出中使用TestSuite
类型。)
以下是您如何使用genTestSuiteFromQcProps
:
-- file Main.hs
{-# OPTIONS_GHC -F -pgmF htfpp #-}
{-# LANGUAGE TemplateHaskell #-}
module Main where
import TH
import Test.Framework
import {-@ HTF_TESTS @-} OtherTests
prop_additionCommutative :: Int -> Int -> Bool
prop_additionCommutative x y = (x + y) == (y + x)
prop_reverseReverseIdentity :: [Int] -> Bool
prop_reverseReverseIdentity l = l == reverse (reverse l)
myTestSuite :: TestSuite
myTestSuite =
$(genTestSuiteFromQcProps
"MyTestSuite"
['prop_additionCommutative
,'prop_reverseReverseIdentity])
main :: IO ()
main = htfMain (myTestSuite : htf_importedTests)
对于您的情况,您可以使用TemplateHaskell传递genTestSuiteFromQcProps
生成的QC属性的名称。
该示例还显示您可以将使用TemplateHaskell函数生成的测试用例与htfpp
收集的测试用例混合使用。为了完整起见,这里是OtherTests
:
{-# OPTIONS_GHC -F -pgmF htfpp #-}
module OtherTests ( htf_thisModulesTests) where
import Test.Framework
test_someOtherTest :: IO ()
test_someOtherTest =
assertEqual 1 1