1

私は初めてでQuickCheck、使い方に頭を悩ませることはできません。

Set誤って(ではなくList)を使用してデータ型を実装したとしましょう。

data Profile = Profile (Set Strategy)
--for completeness:
data Strategy = Strategy Int

その後、このバグに遭遇しました。2つのオブジェクトは、そうではない場合でも同じです。

Profile (Set.fromList [1,2,3]) == Profile (Set.fromList [2,1,3])
-- D'OH! Order doesn't matter in sets!

QuickCheckこのケースをテストするためのテストケースを作成するにはどうすればよいですか?擬似コードでは、これは次のようになります。

assertNotEqual(Profile (Set.fromList [1,2,3]), Profile (Set.fromList [2,1,3]))
assertEqual(Profile (Set.empty), Profile (Set.empty ))

プロジェクトのgithubで例を見てみましたが、そのような些細なケースはカバーされていないようです。

ヒントは大歓迎です!

4

3 に答える 3

3

SmallCheckでサポートされている存在記号を使用してこれを行うことができます。

> depthCheck 5 $ exists $ \xs -> (xs :: [Integer]) /= sort xs
    Depth 5:
      Completed 1 test(s) without failure.
> depthCheck 5 $ exists $ \xs -> fromList (xs :: [Integer]) /= fromList (sort xs)
    Depth 5:
      Failed test no. 1. Test values follow.
      non-existence

全称記号を使用する別のオプション(QuickCheckでも機能します):

> smallCheck 5 $ \xs ys -> xs /= ys ==> fromList (xs :: [Integer]) /= fromList ys
Depth 0:
  Completed 1 test(s) without failure.
  But 1 did not meet ==> condition.
Depth 1:
  Completed 4 test(s) without failure.
  But 2 did not meet ==> condition.
Depth 2:
  Failed test no. 26. Test values follow.
  [0]
  [0,0]
于 2013-02-17T08:26:13.580 に答える
3

このケースをテストするためのQuickCheckテストケースを作成するにはどうすればよいですか?

あなたはすべきではありません!QuickCheckは、プロパティベースのテスト用のツールです。プロパティベースのテストでは、データ構造(またはその他)のプロパティを指定すると、テストツールが自動的にテストケースを生成して、そのプロパティが生成されたテストケースに当てはまるかどうかを確認します。では、のような具体的なテストケースを与える代わりに、どのようにプロパティを与えることができるのか、[1,2,3]そしてなぜプロパティが有利なのかを見てみましょう!

それで。私はから始めました

import Test.QuickCheck
import qualified Data.Set as Set 
import Data.Set (Set)

data Profile = Profile (Set Int)
  deriving (Eq, Show)

mkProfile :: [Int] -> Profile
mkProfile = Profile . Set.fromList

-- | We will test if the order of the arguments matter.
test_mkProfile :: [Int] -> Bool
test_mkProfile xs = (mkProfile xs `comp` mkProfile (reverse xs))
  where comp | length xs <= 1 = (==)
             | otherwise      = (/=)

これが私が自分のプロパティを推論した方法です。空のシングルトンリストの場合、それは単なるIDなので、と同じであるとreverse期待します。右?つまり、まったく同じ引数を取得します。その場合は明らかにそうではありません。のように。そして、プロファイル順序を気にすることを私たちは知っています。mkProfile xsmkProfile (reverse xs)mkProfilelength xs >= 2reverse xsxsreverse [1, 2] /= [2, 1]

では、これを試してみましょうghci

*Main> quickCheck test_mkProfile 
*** Failed! Falsifiable (after 3 tests and 1 shrink):     
[0,0]

ここで、コードには実際には2つの間違いがあることに注意してください。1つは、まず、Profileセットではなくリストを使用する必要があります。第二に、私たちの財産は間違っています!たとえlength xs >= 2xs == reverse (xs)が真実である可能性があるからです。最初のエラーを修正して、クイックチェックで2番目の欠陥がどのように指摘されるかを見てみましょう。

data Profile2 = Profile2 [Int]
  deriving (Eq, Show)

mkProfile2 :: [Int] -> Profile2
mkProfile2 = Profile2 

-- | We will test if the order of the arguments matter.
test_mkProfile2 :: [Int] -> Bool
test_mkProfile2 xs = (mkProfile2 xs `comp` mkProfile2 (reverse xs))
  where comp | length xs <= 1 = (==)
             | otherwise      = (/=)

コードは正しいのですが、プロパティに欠陥があることを忘れないでください。

*Main> quickCheck test_mkProfile2
+++ OK, passed 100 tests.
*Main> quickCheck test_mkProfile2
+++ OK, passed 100 tests.
*Main> quickCheck test_mkProfile2
+++ OK, passed 100 tests.
*Main> quickCheck test_mkProfile2
+++ OK, passed 100 tests.
*Main> quickCheck test_mkProfile2
+++ OK, passed 100 tests.
*Main> quickCheck test_mkProfile2
+++ OK, passed 100 tests.
*Main> quickCheck test_mkProfile2
+++ OK, passed 100 tests.
*Main> quickCheck test_mkProfile2
*** Failed! Falsifiable (after 8 tests):                   
[-8,-8]

はい。あなたはまだ考える必要があります!または、コードが文字通り700のテストケースに合格したという理由だけで、すべてが問題ないという誤った印象を受ける可能性があります。では、プロパティも修正しましょう。

test_mkProfile2_again :: [Int] -> Bool
test_mkProfile2_again xs = (mkProfile2 xs `comp` mkProfile2 ys)
  where ys   = reverse xs
        comp | xs == ys  = (==)
             | otherwise = (/=)

それが複数回機能することを見てみましょう!

*Main> import Control.Monad
*Main Control.Monad> forever $ quickCheck test_mkProfile2_again
+++ OK, passed 100 tests.
+++ OK, passed 100 tests.
+++ OK, passed 100 tests.
+++ OK, passed 100 tests.
+++ OK, passed 100 tests.
+++ OK, passed 100 tests.
+++ OK, passed 100 tests.
... (a lot of times)

やったー。これで、実装のバグが解消されただけでなくProfile、コードとそれに準拠するプロパティについての理解も深まりました。

于 2013-02-17T14:54:27.643 に答える
2

私がコメントしたように、この質問に答える私の主な問題は、あなたのProfileタイプの周りの構造の欠如です。プロファイル、一連の操作、および不変条件を定義すると、クイックチェックテストを簡単に行うことができます。

たとえば、プロファイル、プロファイルを作成する方法、およびプロファイルを変更する1つの方法があるとします。プロパティはすべて一意になります

 module Profile (Profile, mkProfile, addItem) where
 import Data.Set

 newtype Profile = Profile { unProfile :: Set Int }
   deriving (Eq, Ord, Show)

 mkProfile :: [Int] -> Profile
 mkProfile = Profile . fromList

 addItem :: Int -> Profile -> Profile
 addItem x = Profile . insert x . unProfile

各操作の前後にプロパティを指定することで、クイックチェックを使用してこのようなADTをテストできます。

 import Test.QuickCheck
 import Profile as P

 prop_unique_list_unique_profile :: [Int] -> [Int] -> Bool
 prop_unique_list_unique_profile xs ys =
    xs /= ys ==> mkProfile xs /= mkProfile ys

 prop_addItem_nonequal :: Int -> [Int] -> Bool
 prop_addItem_nonequal x xs = P.addItem x xs /= xs
于 2013-02-17T18:46:21.930 に答える