HTF不测试TH生成的道具

时间:2015-09-25 14:07:09

标签: haskell template-haskell quickcheck htf

我想在我的库中对各种类型进行一些类似的测试。

为简化起见,假设我有许多实现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,但我还没有弄清楚如何做到这一点; 它看起来不是一个很好的清洁解决方案。

3 个答案:

答案 0 :(得分:3)

如果事先知道生成的属性测试的名称是什么,那么你总是可以手动定义存根,以便HTF看到它们,例如:

$(generate prop test for Int)
$(generate prop test for CInt)

prop_p1 = prop_absNormInt
prop_p2 = prop_absNormCInt

HTF会将测试视为prop_p1prop_p2。您不必在这些存根上添加类型签名。

另一个想法是创建自己的源预处理器来为您添加这些存根(并给它们更好的名称)。您的源预处理器会自动调用htfpp来完成预处理。

如果您告诉我如何调用TH,我可以向您展示如何编写预处理器。

更新

鉴于你的评论,我会考虑做以下事情:

  1. 编写程序以生成测试模块源。
  2. 包括该程序及其在您的cabal项目中生成的输出。
  3. 如果用户想要更新测试模块,请告诉用户运行该程序。
  4. 所以 - 在运行程序以重新生成测试模块之前,测试用例保持不变。

    拥有静态测试模块的优势在于您可以确切地确定正在测试的内容。

    拥有一个重新创建测试模块的程序,您可以在新的Num实例可用时轻松更新它。

答案 1 :(得分:1)

好的,我设法解决了这个问题。 我们的想法是使用TH来聚合测试并将它们插入到htfMain中。 除了我在问题中的内容之外,还包括以下步骤:

  1. 将所有可测试的属性转换为运行QuickCheck测试的IO操作;
  2. 将所有测试汇总到TestSuite;
  3. 将所有测试套件聚合到一个列表中,并将其放入htfMain
  4. 为了使用步骤1,我必须使用名为qcAssertion :: (QCAssertion t) => t -> Assertion的HTF的半内部功能。 此功能可用,但不建议外部使用;它允许很好地运行QuickCheck测试,将它们集成到报告中。

    要继续执行第2步,我使用了HTF中的两个函数:makeTestSuitemakeQuickCheckTest。 我还使用来自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