![](/img/trans.png)
[英]Haskell Test Framework (HTF) and test definitions in multiple modules
[英]HTF does not test props generated by TH
我想對我的庫中的各種類型進行許多類似的測試。
為簡化起見,假設我有許多實現Num
類的向量類型,並且我想生成相同的prop_absNorm xy = abs x + abs y >= abs (x+y)
屬性檢查prop_absNorm xy = 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
查找我的矢量類VectorMath (n::Nat) t
所有實例(並同時reify ''VectorMath
Num
實例),並相應地生成所有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生成的道具,那么是否可以對各種類型進行prop_absNorm :: Vector 4 a -> Vector 4 a -> Bool
測試(即將其替換為prop_absNorm :: Vector 4 a -> Vector 4 a -> Bool
)?
另外一種替代方法是進一步使用TH將測試條目手動添加到htf_Main中,但是我還沒有弄清楚如何執行此操作。 而且它看起來並不是一個很好的干凈解決方案。
如果您事先知道生成的屬性測試的名稱是什么,那么您始終可以手動定義存根,以便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實例可用時輕松地對其進行更新。
好的,我設法解決了這個問題。 想法是使用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)
我現在能夠生成和測試因參數類型而異的屬性-對於同一類型作用域中可用的所有類型。 測試結果和日志的記錄方式與原始測試相同。
至此,問題得到了徹底解決。
HTF不使用TemplateHaskell收集測試,這會大大降低編譯時間。 取而代之的是,HTF使用名為htfpp
的自定義預處理器。 htfpp
在編譯器之前運行(因此在TemplateHaskell接頭擴展之前運行)。 這意味着在使用TemplateHaskell生成測試時, htfpp
在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
預處理程序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
收集的htfpp
。 為了完整OtherTests
,這是OtherTests
的內容:
{-# OPTIONS_GHC -F -pgmF htfpp #-}
module OtherTests ( htf_thisModulesTests) where
import Test.Framework
test_someOtherTest :: IO ()
test_someOtherTest =
assertEqual 1 1
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.