2012-04-06 19 views
15

Właśnie zacząłem używać QuickCheck z kodem Haskella. Jestem w tyle, wiem. To pytanie jest dwuczęściowe: Najpierw, jakie są ogólne sprawdzone metody szybkiego sprawdzania? Do tej pory, ja podniósł następujące:Sprawdzone metody Haskell QuickCheck (szczególnie podczas testowania klas typów)

  • imię Twój testy prop_ * (irytujące, ponieważ wszystko inne jest camelCase)
  • test eksportowane kod (jeśli badanie wewnętrzne jesteś prawdopodobnie robi źle)
  • Właściwości testowe, a nie przykłady
    • nie mów X is out of range, Y is in range
    • Zamiast powiedzieć if x is out of range, normalize x ≠ x (lub jakąś inną nieruchomość)

Ale wciąż chwytam się innych najlepszych praktyk. W szczególności:

  • Gdzie są przechowywane nieruchomości?
    • Ten sam plik?
    • w katalogu test/? (Jeśli tak, to w jaki sposób można zaimportować elementy w katalogu src/?)
    • w katalogupod numerem src?

Co najważniejsze, w jaki sposób mamy tendencję, aby przejść o badania właściwości na zajęciach typu? Na przykład, należy rozważyć następujące klasy (uproszczony) typ:

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

chciałbym przetestować właściwość ∀ x: prev (next x) == x. Oczywiście wiąże się to z pisaniem testów dla każdej instancji. Nużące jest pisanie tej samej właściwości dla każdej instancji, szczególnie gdy test jest bardziej skomplikowany. Jaki jest standardowy sposób generalizowania takich testów?

+4

Dla importu równoległego przy użyciu 'src /' i '' test/katalogów, będziemy chcieli, aby ustawić 'Hs-source-Dirs: src, test' w' .cabal' pliku tak, że oba katalogi znajdują się w ścieżce wyszukiwania modułów. – hammar

+2

Dlaczego internals nie mają właściwości? – alternative

+0

Z pewnością mogą, trudniej jest uzyskać dla nich testy i (z mojego doświadczenia) bardziej przydatne jest przetestowanie wyeksportowanego zachowania zamiast szczegółów implementacji. – So8res

Odpowiedz

10

Wierzę, że konwencja prop_ pochodzi od QC przychodzącej ze skryptem, który uruchomił wszystkie funkcje, które rozpoczęły się od testów jako prop_. Tak więc nie ma żadnego powodu, aby to zrobić, ale to znaczy, że wizualnie się wyróżnia (więc właściwość dla funkcji foo to prop_foo).

I nie ma nic złego w testowaniu wewnętrznych elementów.Można to zrobić na dwa sposoby:

  • Umieść właściwości w tym samym module co elementy wewnętrzne. To sprawia, że ​​moduł jest większy i wymaga bezwarunkowej zależności od QC dla projektu (chyba że używasz hackery CPP).

  • Mieć elementy wewnętrzne w module nie wyeksportowanym, a funkcje do wyeksportowania zostały ponownie wyeksportowane z innego modułu. Następnie możesz zaimportować moduł wewnętrzny do tego, który definiuje właściwości QC, a ten moduł jest tylko zbudowany (i ma zależność QC), jeśli użyta flaga podana w pliku .cabal.

Jeśli projekt jest duża, wówczas mający odrębną src/ i test/ katalogi mogą być przydatne (choć o rozróżnienie może uniemożliwić testów wewnętrznych). Ale jeśli twój projekt nie jest aż tak duży (i tak czy inaczej znajduje się w ramach ogólnej hierarchii modułów), to nie ma takiej potrzeby, aby to rozdzielić.

Jak powiedział Norman Ramsey w swojej odpowiedzi, w przypadku klas typu można po prostu zdefiniować właściwość jako na na typeclass i użyć odpowiednio.

+0

Coś, co często robimy, to sekcja "test-suite" Cabal, która zależy bezpośrednio od wewnętrznych modułów (a nie od biblioteki zdefiniowanej w tym samym pliku .cabal) i w ten sposób możesz je przetestować kosztem dodatkowej kompilacji czas. – tibbe

15

To nużące napisać tę samą właściwość dla każdej instancji

Nie rób tego. Piszesz właściwość raz dla klasy:

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 

Potem go przetestować, rzucasz do konkretnego typu:

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

Twoje inne pytania nie mogę pomóc z.

3

Spróbuj

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

Klasa typ reified się w życiowo ilościowo słownika, w których wystąpienie Arbitrary jest określona. Ta instancja słownikowa Arbitrary jest następnie używana do definiowania instancji o wartości Arbitrary dla wartości zliczanych z zewnątrz.

Kolejny przykład podano pod adresem https://github.com/sonyandy/var/blob/4e0b12c390eb503616d53281b0fd66c0e1d0594d/tests/properties.hs#L217.

To może być dalej uogólnione (i skrócony schemat) jeśli chcesz użyć ConstraintKinds. Poniższe definicje są zdefiniowane tylko jeden raz.

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 

Dla każdej klasy typu chcesz przetestować, wymagane jest Arbitrary instancja Dict.

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 
Powiązane problemy