5

ライブラリ内のさまざまなタイプに対して、同様のテストを多数実行したいと考えています。

簡単にするために、クラスを実装する多くのベクター型があり、ライブラリ内のすべての型で機能する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) を検索し、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 式$(..)は他の props と同じファイルにあります)。

注 2: prop 関数を作成するための型のリストは可変です。後で の他のインスタンスを追加したいVectorMathので、テスト リストに自動的に追加されます。

問題は、HTF(おそらくTHも使用する)が生成されたコードを含むファイルではなく、元のファイルを解析することだと思いますが、なぜこれが起こるのかわかりません。

だから私の質問は: この問題を解決するにはどうすればよいですか? TH で生成された props を使用できない場合、さまざまなタイプで QuickCheck テストを実行できますか (つまり、それらを に代入しますprop_absNorm :: Vector 4 a -> Vector 4 a -> Bool)?

また、別の方法として、TH をさらに使用して手動でテスト エントリを htf_Main に追加することもできますが、これを行う方法はまだわかりません。そして、それはきれいな解決策のようには見えません。

4

3 に答える 3

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ます。これらのスタブに型シグネチャを付ける必要はありません。

もう 1 つのアイデアは、独自のソース プリプロセッサを作成して、これらのスタブを追加する (さらに適切な名前を付ける) ことです。ソースのプリプロセッサはhtfpp、前処理を完了するために自動的に呼び出します。

TH がどのように呼び出されるかを教えていただければ、プリプロセッサの書き方をお見せできます。

アップデート:

あなたのコメントを考えると、私は次のことを検討します:

  1. テスト モジュール ソースを生成するプログラムを作成します。
  2. そのプログラムとそれが生成する出力を cabal プロジェクトに含めます。
  3. テスト モジュールを更新する場合は、プログラムを実行するようにユーザーに指示します。

つまり、プログラムを実行してテスト モジュールを再生成するまで、テスト ケースは修正されたままになります。

静的テスト モジュールを使用すると、何がテストされているかを正確に把握できるという利点があります。

テスト モジュールを再作成するプログラムがあると、新しい Num インスタンスが利用可能になったときに簡単に更新できます。

于 2015-09-25T15:21:43.907 に答える
1

わかりました、私はこの問題を解決することができました。アイデアは、TH を使用してテストを集約し、それらを に挿入することhtfMainです。質問にあるものに加えて、これには次の手順が含まれます。

  1. IOすべてのテスト可能なプロパティを、QuickCheck テストを実行するアクションに変換します。
  2. すべてのテストを に集約しTestSuiteます。
  3. すべてのテスト スイートを 1 つのリストに集約し、 に入れますhtfMain

ステップ 1 を使用するには、HTF と呼ばれる半内部関数を使用する必要がありましたqcAssertion :: (QCAssertion t) => t -> Assertion。この機能は利用可能ですが、外部での使用は推奨されません。QuickCheck テストを適切に実行し、それらをレポートに統合することができます。

ステップ 2 に進むために、HTF の 2 つの関数 と を使用makeTestSuitemakeQuickCheckTestます。また、TH の関数を使用locationして、テスト テンプレートを使用したスプライスが挿入された場所のファイル名と行を提供します (より良いテスト ログのために)。

ステップ 3 は難しいものです。このためには、生成されたすべてのテスト スイートを見つける必要があります。問題は、TH がモジュール内のすべての関数 (生成されたものを含む) をブラウズできないことです。これを克服するために、次の型クラスを追加しました。

class MultitypeTestSuite name where
    multitypeTestSuite :: name -> TestSuite

したがって、私の関数writeTestsは新しいデータ型data MTS[prop_name]とそのデータ型のインスタンスを生成しますMultitypeTestSuite。これにより、後で htfMain で別の splice 関数を使用できます。この関数は、次を使用してそのクラスのインスタンスからテスト スイートのリストを生成します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-27T08:41:49.770 に答える