2012-04-06 12 views
15

Ich habe gerade angefangen, QuickCheck mit einer Menge Haskell-Code zu verwenden. Ich bin hinter der Zeit zurück, ich weiß. Diese Frage ist ein zweiteiliger:Haskell QuickCheck Best Practices (besonders beim Testen von Klassen)

Erstens, was sind die allgemeinen Best Practices für Quick Check? Bisher habe ich folgendes aufgenommen:

  • Namen Ihre Tests prop_ * (ärgerlich, denn alles andere ist Camelcase)
  • -Test exportierten Code (wenn Sie Interna sind Testen Sie wahrscheinlich, es zu tun falsch)
  • Testeigenschaften, keine Beispiele
    • sagen Sie nicht X is out of range, Y is in range
    • Stattdessen sagen if x is out of range, normalize x ≠ x (oder eine andere solche Eigenschaft)

Aber ich fasse immer noch andere Best Practices. Besonders:

  • Wo bleiben die Eigenschaften?
    • Die gleiche Datei?
    • in einem Verzeichnis test/? (Wenn ja, wie importieren Sie dann das Zeug in src/?)
    • in einem Properties/ Verzeichnis unter src?

Am wichtigsten ist, wie wir neigen dazu, über die Prüfung Eigenschaften auf Typklassen zu gehen? Betrachten wir zum Beispiel die folgende (vereinfachte) Typklasse:

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

ich die Eigenschaft ∀ x: prev (next x) == x testen möchten. Natürlich beinhaltet dies das Schreiben von Tests für jede Instanz. Es ist mühsam, die gleiche Eigenschaft für jede Instanz zu schreiben, besonders wenn der Test komplizierter ist. Wie kann man solche Tests standardisieren?

+4

Für die Einfuhr, wenn parallel mit 'src /' und 'Test /' Verzeichnisse, Sie wollen 'Hs-Quelle-Dirs einzustellen: src, test' in Ihrer' .cabal' Datei, so dass beide Verzeichnisse sind im Modul Suchpfad. – hammar

+2

Warum können Interna keine Eigenschaften haben? – alternative

+0

Sie können es sicherlich, es ist nur schwieriger, Tests zu ihnen zu bekommen und (nach meiner Erfahrung) ist es viel nützlicher, exportierte Verhalten anstelle von Implementierungsdetails zu testen. – So8res

Antwort

10

Ich glaube, die prop_ Konvention kam von QC kommt mit einem Skript, das alle Funktionen, die mit prop_ als Tests gestartet wurden. Es gibt also keinen wirklichen Grund, dies zu tun, aber es macht visuell herausstechen (so ist die Eigenschaft für eine Funktion foo ist prop_foo).

Und es ist nichts falsch mit Tests Internals.Dazu gibt es zwei Möglichkeiten:

  • Legen Sie die Eigenschaften in das gleiche Modul wie die Interna. Dies macht das Modul größer und erfordert eine bedingungslose Abhängigkeit von QC für das Projekt (es sei denn, Sie verwenden CPP-Hacker).

  • Haben Interna in einem nicht exportierten Modul, mit den Funktionen, die tatsächlich exportiert werden, von einem anderen Modul wieder exportiert. Dann können Sie das interne Modul in eines importieren, das die QC-Eigenschaften definiert, und dieses Modul wird nur erstellt (und hat eine QC-Abhängigkeit), wenn ein in der .cabal-Datei spezifiziertes Flag verwendet wird.

Wenn Ihr Projekt groß ist, dann mit getrennten src/ und test/ Verzeichnisse nützlich sein können (obwohl eine Unterscheidung, die verhindert, dass Sie von der Prüfung Einbauten). Aber wenn Ihr Projekt nicht so groß ist (und trotzdem unter einer gesamten Modulhierarchie liegt), dann brauchen Sie es nicht wirklich zu teilen.

Wie Norman Ramsey in seiner Antwort sagte, können Sie für Typklassen einfach die Eigenschaft als auf der Typklasse definieren und entsprechend verwenden.

+0

Etwas, was wir oft machen, ist ein Cabal 'Testsuite'-Abschnitt, der direkt von den internen Modulen abhängt (und nicht von der Bibliothek, die in derselben .cabal-Datei definiert ist) und auf diese Weise können Sie diese auf Kosten einer zusätzlichen Kompilierung testen Zeit. – tibbe

15

Es ist mühsam die gleiche Eigenschaft für jede Instanz

Sie nicht tun, dies zu schreiben. Sie schreiben die Eigenschaft einmal für die Klasse:

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 

Dann ist es zu testen, werfen Sie einen bestimmten Typ:

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

Ihre anderen Fragen kann ich mit nicht helfen.

3

Versuchen

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

Die Typklasse in eine existenzquantifizierte Wörterbuch verdinglicht, auf dem ein Arbitrary Instanz definiert ist. Diese Arbitrary Wörterbuchinstanz wird dann verwendet, um eine Instanz von Arbitrary für existenziell quantifizierte Werte zu definieren.

Ein anderes Beispiel wird bei https://github.com/sonyandy/var/blob/4e0b12c390eb503616d53281b0fd66c0e1d0594d/tests/properties.hs#L217 gegeben.

Dies kann weiter verallgemeinert werden (und das Boilerplate reduziert), wenn Sie bereit sind, ConstraintKinds zu verwenden. Folgendes ist nur einmal definiert.

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 

Für jede Klasse, die Sie ist eine Arbitrary Instanz von Dict erforderlich testen möchten.

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 
Verwandte Themen