2012-04-06 36 views
15

我剛開始使用QuickCheck和一堆Haskell代碼。我知道,我落後於時代。這個問題是雙方的:Haskell QuickCheck最佳實踐(尤其是在測試類型類時)

首先,快速檢查的一般最佳做法是什麼?到目前爲止,我拿起了以下幾點:

  • 名稱你的測試prop_ *(討厭的,因爲一切是駝峯)
  • 測試代碼出口(如果你正在測試的內部,你很可能會做錯)
  • 試驗性質,而不是一個例子
    • 不要說X is out of range, Y is in range
    • 而是說:if x is out of range, normalize x ≠ x(或其它財產)

但我仍然在抓其他最佳實踐。特別是:

  • 屬性保存在哪裏?
    • 相同的文件?
    • test/目錄中? (如果是這樣,那麼你怎麼進口src/的東西?)
    • Properties/目錄下src

更重要的是,我們如何傾向於去對類型類測試性能?例如,考慮以下的(簡化的)類型的類:

class Gen a where 
    next :: a -> a 
    prev :: a -> a 

我想測試屬性∀ x: prev (next x) == x。當然,這涉及到爲每個實例編寫測試。爲每個實例編寫相同的屬性非常繁瑣,特別是當測試更復雜時。概括這些測試的標準方法是什麼?

+4

對於進口使用並行'SRC /'和'測試/'目錄時,你要設置'HS-來源,迪爾斯:SRC,test'在'.cabal'文件,使這兩個目錄在模塊搜索路徑中。 – hammar 2012-04-06 01:29:48

+2

爲什麼內部不能有屬性? – alternative 2012-04-06 11:10:13

+0

他們當然可以,只是難以得到測試,並且(以我的經驗),測試導出行爲而不是實現細節會更有用。 – So8res 2012-04-06 13:28:00

回答

10

我相信prop_慣例來自QC隨着腳本運行的所有功能,開始prop_作爲測試。所以沒有真正的理由這樣做,但它確實視覺上脫穎而出(因此功能foo的屬性是prop_foo)。

測試內部結構沒有任何問題。有兩種方法可以這樣做:

  • 將屬性放在與內部相同的模塊中。這使模塊更大,並且需要項目對QC的無條件依賴(除非您使用CPP hackery)。

  • 在非導出模塊中具有內部功能,實際上可以從其他模塊導出功能。然後,您可以將內部模塊導入定義QC屬性的模塊,並且如果使用.cabal文件中指定的標誌,則該模塊僅被構建(並且具有QC依賴性)。

如果你的項目非常大,則有獨立的src/test/目錄可能是有用的(雖然有區別可能會阻止您測試內部)。但是如果你的項目不是那麼大(並且無論如何都駐留在一個整體模塊層次結構中),那麼就沒有必要像這樣分割它。

正如Norman Ramsey在他的回答中所說的,對於類型類,您可以將屬性定義爲, typeclass並相應地使用。

+0

我們經常做的事情就是擁有一個Cabal'test-suite'部分,它直接取決於內部模塊(而不是在同一個.cabal文件中定義的庫),並且您可以以某些額外編譯爲代價來測試這些部分時間。 – tibbe 2013-05-28 23:34:54

15

這是乏味寫相同的屬性爲每個實例

你不這樣做。你寫的屬性曾經爲類:

class Gen a where 
    next :: a -> a 
    prev :: a -> a 

np_prop :: (Eq a, Gen a) => a -> Bool 
np_prop a = prev (next a) == a 

然後測試它,你投給特定類型:

quickCheck (np_prop :: Int -> Bool) 
quickCheck (np_prop :: String -> Bool) 

你的其他問題,我不能幫助。

3

嘗試

{-# LANGUAGE GADTs, ScopedTypeVariables #-} 
import Test.QuickCheck hiding (Gen) 

class Gen a where 
    next :: a -> a 
    prev :: a -> a 

np_prop :: SomeGen -> Bool 
np_prop (SomeGen a) = prev (next a) == a 

main :: IO() 
main = quickCheck np_prop 

instance Gen Bool where 
    next True = False 
    next False = True 
    prev True = False 
    prev False = True 

instance Gen Int where 
    next = (+ 1) 
    prev = subtract 1 

data SomeGen where 
    SomeGen :: (Show a, Eq a, Arbitrary a, Gen a) => a -> SomeGen 

instance Show SomeGen where 
    showsPrec p (SomeGen a) = showsPrec p a 
    show (SomeGen a) = show a 

instance Arbitrary SomeGen where 
    arbitrary = do 
    GenDict (Proxy :: Proxy a) <- arbitrary 
    a :: a <- arbitrary 
    return $ SomeGen a 
    shrink (SomeGen a) = 
    map SomeGen $ shrink a 

data GenDict where 
    GenDict :: (Show a, Eq a, Arbitrary a, Gen a) => Proxy a -> GenDict 

instance Arbitrary GenDict where 
    arbitrary = 
    elements 
    [ GenDict (Proxy :: Proxy Bool) 
    , GenDict (Proxy :: Proxy Int) 
    ] 

data Proxy a = Proxy 

類型類物化到存在性量化詞典,在其上定義一個Arbitrary實例。然後使用此Arbitrary字典實例爲存在量化值定義Arbitrary的實例。

另一個例子在https://github.com/sonyandy/var/blob/4e0b12c390eb503616d53281b0fd66c0e1d0594d/tests/properties.hs#L217處給出。

如果您願意使用ConstraintKinds,這可以進一步推廣(並減少樣板文件)。以下僅定義一次。

data Some c where 
    Some :: (Show a, Arbitrary a, c a) => a -> Some c 

instance Show (Some c) where 
    showsPrec p (Some a) = showsPrec p a 
    show (Some a) = show a 

instance Arbitrary (Dict c) => Arbitrary (Some c) where 
    arbitrary = do 
    Dict (Proxy :: Proxy a) :: Dict c <- arbitrary 
    a :: a <- arbitrary 
    return $ Some a 
    shrink (Some a) = 
    map Some $ shrink a 

data Dict c where 
    Dict :: (Show a, Arbitrary a, c a) => Proxy a -> Dict c 

data Proxy a = Proxy 

class (c a, d a) => (c &&# d) a 
instance (c a, d a) => (c &&# d) a 

對於要測試每種類型的類的Dict一個Arbitrary實例是必要的。

instance Arbitrary (Dict (Eq &&# Gen)) where 
    arbitrary = 
    elements 
    [ Dict (Proxy :: Proxy Bool) 
    , Dict (Proxy :: Proxy Int) 
    ] 

np_prop :: Some (Eq &&# Gen) -> Bool 
np_prop (Some a) = prev (next a) == a