5

私はおもちゃのプログラムを持っています:

$ cat a.hs
main = putStrLn "Toy example"
$ runghc a.hs
Toy example

テンプレート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つで十分ですが、両方の末尾の行をインデントする必要があります。

モジュールのほとんどをインデントする必要を回避できますか?(私の実際のモジュールには、1行以上のコードがあります。)(そして、{ ; ; }表記法を使用せずに?)

すべてのモジュール宣言を引用符で囲みたいのですが、通常のコード(...)では、に置き換えることができますが、閉じ括弧とインデントを回避できる$ ...同等のものはありますか?[d|...|]

または、モジュールAが、AがインポートされたモジュールBの最上位の宣言が、関数Aがエクスポートすることによって自動的に処理されると言うことができる方法はありますか?

ノート:

  1. 私のRealProgramのテンプレートHaskellは、より複雑ですid—宣言をスキャンして開始する変数名をprop_探し、それらを含むテストスイートを構築します。ソースファイルを直接変更せずに、代わりにこれを行うことができる他の純粋なHaskellの方法はありますか?
  2. GHCv6.12.1を使用しています。GHC v7.0.3を使用すると、b.hsのエラーが別の場所で報告されます— b.hs:3:1—ただし、それ以外の動作は同じです。
4

2 に答える 2

4

テストスイートがQuickCheck用の場合は、All代わりに新しいモジュールを使用することをお勧めします:http: //hackage.haskell.org/packages/archive/QuickCheck/2.4.1.1/doc/html/Test-QuickCheck-All.html

ファイルシステムにアクセスし、スプライスが存在するファイルを解析することによってプロパティの名前をフェッチすることを除いて、同じことを行います(他のテストフレームワークを使用している場合でも、同じアプローチを使用できます)。

本当にファイル全体を引用したい場合は、代わりに準引用符を使用できます(インデントは必要ありません)。haskell-src-metaでquoterを簡単に構築できますが、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)) |]

runQuickCheckAllAllからエクスポートされない関数も必要です。

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
于 2011-10-01T10:35:22.090 に答える
3

[私のプログラム]は、prop_で始まる変数名の宣言をスキャンし、それらを含むテストスイートを構築します。ソースファイルを直接変更せずに、代わりにこれを行うことができる他の純粋なHaskellの方法はありますか?

はいあります!パッケージを使用language-haskell-extractます。

{-# 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.

ただし、車輪の再発明を行う前に、test-framework-thパッケージを確認することをお勧めします。これは、ほぼ正確にこれを実行しますが、HUnitもサポートし、優れたテストランナー(色付き!)を備えています。

{-# 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複数のファイルからのテストを組み合わせたい場合に便利なもあります。

于 2011-10-01T16:47:35.677 に答える