我想在我的库中对各种类型做一些类似的测试。
为了简化操作,假设我有许多实现Num类的向量类型,并且我想要生成相同的QuickCheck属性检查prop_absNorm x y = abs x + abs y >= abs (x+y),它可以在库中的所有类型上工作。
我使用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的所有实例(同时查找Num实例),并相应地生成所有支持函数。-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:用于创建支柱函数的类型列表是可变的--我希望稍后添加其他VectorMath实例,因此它们将自动添加到测试列表中。
我认为问题在于HTF (大概也使用TH )解析了原始文件,而不是生成代码的文件--但我不明白为什么会发生这种情况。
所以我的问题是:如何解决这个问题?如果不可能使用TH生成的道具,那么是否可以对各种类型进行QuickCheck测试(即将它们替换为prop_absNorm :: Vector 4 a -> Vector 4 a -> Bool)?
另外,另一种选择可能是进一步使用TH将测试条目手动添加到htf_Main中,但我还没有弄清楚如何做到这一点;而且它看起来并不是一个很好的干净解决方案。
发布于 2015-09-27 08:41:49
好吧,我解决了这个问题。其想法是使用TH聚合测试并将它们插入到htfMain中。除了我在问题中的内容外,还包括以下步骤:
IO测试的QuickCheck操作;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),我现在能够为同一类型范围内的所有类型生成和测试不同参数类型的属性。测试结果和日志包含的方式与原始测试相同。
在这一点上,问题完全解决了。
发布于 2015-09-25 15:21:43
如果预先知道生成的属性测试的名称,则可以手动定义存根,以便HTF看到它们,例如:
$(generate prop test for Int)
$(generate prop test for CInt)
prop_p1 = prop_absNormInt
prop_p2 = prop_absNormCIntHTF将把测试看作是prop_p1和prop_p2。您不应该将类型签名放在这些存根上。
另一个想法是创建您自己的源预处理器,为您添加这些存根(并给它们更好的名称)。您的源预处理器将自动调用htfpp来完成预处理.
如果你告诉我你的TH是如何被调用的,我可以告诉你如何编写预处理器。
更新:
考虑到你的评论,我想做以下几点:
因此-测试用例保持固定,直到程序运行以重新生成测试模块。
拥有一个静态测试模块有一个优点,您可以准确地知道正在测试的是什么。
有了一个重新创建测试模块的程序,您就可以在新的Num实例可用时轻松地更新它。
发布于 2015-09-27 08:51:13
HTF不使用TemplateHaskell来收集测试,这将大大减慢编译时间。相反,HTF使用名为htfpp的自定义预处理器。htfpp在编译器之前运行(因此在扩展TemplateHaskell剪接之前)。这意味着在用htfpp生成测试时,不能在TemplateHaskell中使用自动测试发现。
我的建议是:无论如何,当您使用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预处理程序als在其输出中使用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)对于您的情况,您将传递给genTestSuiteFromQcProps您用TemplateHaskell生成的QC属性的名称。
该示例还显示,您可以将使用TemplateHaskell函数生成的测试用例与htfpp收集的测试用例混合使用。为了完整起见,以下是OtherTests的内容
{-# OPTIONS_GHC -F -pgmF htfpp #-}
module OtherTests ( htf_thisModulesTests) where
import Test.Framework
test_someOtherTest :: IO ()
test_someOtherTest =
assertEqual 1 1https://stackoverflow.com/questions/32784054
复制相似问题