Quickcheck Funktionale Programmierung Fallstudie Quickcheck D. Rösner Institut für Wissens- und Sprachverarbeitung Fakultät für Informatik Otto-von-Guericke Universität Magdeburg c Sommer 2014, 20. Juni 2014, 2011 - 14 D.Rösner D. Rösner FP 2014 . . . 1 Quickcheck Gliederung 1 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte D. Rösner FP 2014 . . . 2 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte Fallstudie: Testen von Haskell-Programmen Programm QuickCheck Quelle: http://www.cs.chalmers.se/~rjmh/QuickCheck in GHCi: import Test.QuickCheck Grundidee: Benutzer definiert Eigenschaften, die ein Programm erfüllen soll diese Eigenschaften werden dann an einer grossen Zahl automatisch generierter Testfälle überprüft ursprünglich für Haskell entwickelt, gibt es Implementationen von QuickCheck mittlerweile für zahlreiche andere Sprachen (u.a. für Erlang) Originalarbeit: [CH00] D. Rösner FP 2014 . . . 4 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte Fallstudie: QuickCheck Beispiel: Definition einer Eigenschaft prop_RevRev xs = reverse(reverse xs) == xs where types = xs::[Int] Testen einer Eigenschaft *Main> quickCheck prop_RevRev +++ OK, passed 100 tests. D. Rösner FP 2014 . . . 5 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte Fallstudie: QuickCheck Beispiel: Definition einer Eigenschaft prop_RevId xs = reverse xs == xs where types = xs::[Int] Testen und Falsifizieren einer Eigenschaft *Main> quickCheck prop_RevId *** Failed! Falsifiable (after 6 tests and 1 shrink): [0,1] *Main> quickCheck prop_RevId *** Failed! Falsifiable (after 4 tests and 2 shrinks) [0,1] D. Rösner FP 2014 . . . 6 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte Fallstudie: QuickCheck Beispiel: ausführliches Ablaufprotokoll: *Main> verboseCheck prop_RevId Passed: [] Passed: [-1] Failed: [0,-1] *** Failed! Passed: [] Passed: [-1] Passed: [0] Failed: [0,1] Passed: [] Passed: [1] Passed: [0] Passed: [0,0] Falsifiable (after 3 tests and 1 shrink): D. Rösner FP 2014 . . . [0,1] 7 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte Fallstudie: Testen von Haskell-Programmen Programm QuickCheck erlaubt Testen bedingter Eigenschaften Testen quantifizierter Eigenschaften Steuerung der Verteilung der Testfälle Klassifizieren und Zählen von Testfällen Sammeln von Datenwerten D. Rösner FP 2014 . . . 9 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Testen bedingter Eigenschaften Eigenschaften können die Form haben <condition> ==> <property> eine solche Eigenschaft gilt genau dann, wenn die Eigenschaft nach dem ==> immer gilt, wenn die <condition> gilt beim Testen werden nur Testfälle betrachtet, welche die <condition> erfüllen ; falls nach einer vorgegebenen Anzahl von Versuchen (derzeit: 1000) noch keine 100 Testfälle gefunden, wird beendet und Meldung ausgegeben wie Arguments exhausted after 97 test. D. Rösner FP 2014 . . . 10 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Testen bedingter Eigenschaften Eigenschaft: prop_Insert x xs = ordered xs ==> collect (length xs)$ ordered (insert x xs) where types = x::Int Testläufe: *Main> quickCheck prop_Insert *** Gave up! Passed only 72 tests: 44% 0 33% 1 16% 2 5% 3 *Main> quickCheck prop_Insert *** Gave up! Passed only 82 tests: 39% 0 32% 1 20% 2 4% 3 2% 4 D. Rösner FP 2014 . . . 11 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Testen quantifizierter Eigenschaften Eigenschaften können die Form haben forAll <generator> $ \<pattern> -> <property> Beispiel: prop_Insert2 x = forAll orderedList $ \xs -> ordered (insert x xs) where types = x::Int das erste Argument des forAll ist ein Testdatengenerator orderedList :: (Ord a, Arbitrary a) => Gen [a] mit solchen speziellen Generatoren lässt sich die Verteilung der Testdaten besser kontrollieren als durch nachträgliches Filtern D. Rösner FP 2014 . . . 12 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Testen quantifizierter Eigenschaften *Main> verboseCheck prop_Insert2 Passed: -1 [] Passed: -1 [-1] ... Passed: 2 [-4,4] Passed: -4 [-4,-4,-3,3] ... Passed: -2 [-15,-10,-6,-5,-3,-3,-3,0,1,10] Passed: -1 [-14,-4,-4,5,8] Passed: -10 [-15,-10,-8,-6,-5,-4,-3,0,4,15,16,16] ... D. Rösner FP 2014 . . . +++ OK, passed 100 tests. 13 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Klassifikation von Testfällen Eigenschaften können die Form haben classify <condition> <string>$ <property> Beispiel: prop_Insert3 x xs = ordered xs ==> classify (ordered (x:xs)) "at-head"$ classify (ordered (xs++[x])) "at-tail"$ ordered (insert x xs) where types = x::Int D. Rösner FP 2014 . . . 14 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Klassifikation von Testfällen Verteilung der Testfälle (die mehreren Kategorien angehören können) wird berichtet Beispiel: *Main> quickCheck prop_Insert3 *** Gave up! Passed only 74 tests: 41% at-head, at-tail 24% at-head 22% at-tail D. Rösner FP 2014 . . . 15 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Klassifikation von Testfällen Eigenschaften können die Form haben classify <condition> <string>$ <property> Beispiel: prop_RemDup4’’ xs = classify (null xs) "empty list" $ classify (length xs == 1) "singleton" $ classify (length xs == 2) "pair list" $ classify (length xs > 2) "three or more elems" $ classify ((length xs > 2) && (allDifferent xs)) "nontrivially all different" $ (noRepetitions (remDuplicates xs)) where types = xs::[Int] D. Rösner FP 2014 . . . 16 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Klassifikation von Testfällen Beispiel: *Main> quickCheck prop_RemDup4’’ +++ OK, passed 100 tests: 81% three or more elems, nontrivially all different 8% three or more elems 6% singleton 4% empty list 1% pair list *Main> quickCheck prop_RemDup4’’ *** Failed! Falsifiable (after 10 tests and 2 shrinks): [-8,-8,-8] getestet wurde die folgende fehlerhafte Implementierung: -- faulty version 2: does miss three consecutives remDuplicates [] = [] remDuplicates [x] = [x] remDuplicates (x:y:xs) = if x == y then y:remDuplicates xs else x : remDuplicates (y:xs) D. Rösner FP 2014 . . . 17 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Sammeln von Datenwerten Eigenschaften können die Form haben collect <expression>$ <property> Beispiel: prop_Insert x xs = ordered xs ==> collect (length xs)$ ordered (insert x xs) where types = x::Int die Verteilung der Werte des Arguments von collect wird berichtet D. Rösner FP 2014 . . . 18 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Sammeln von Datenwerten Beispiel: prop_RevRevColl xs = collect (length xs) $ reverse(reverse xs) == xs where types = xs::[Int] *Main> quickCheck prop_RevRevColl +++ OK, passed 100 tests: 6% 1 5% 13 4% 5 4% 39 4% 23 4% 2 4% 12 3% 4 3% 35 3% 31 3% 3 3% 26 ... 1% 27 1% 18 1% 16 1% 10 D. Rösner FP 2014 . . . 19 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Datenstrukturen Beispiel: data Card = Card Int String deriving (Eq,Show) für das Erzeugen zufälliger Instanzen: instance Arbitrary Card where arbitrary = do int <- arbitrary -- of type Gen Int string <- arbitrary -- of type Gen String return (Card int string) [Tho11], Ch. 19 D. Rösner FP 2014 . . . 21 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Datenstrukturen Beispiel: *Main> sample (arbitrary :: Gen Card) Card 1 "" Card (-1) "" Card (-1) "?" Card 2 "\201B\170\v" Card (-4) "\232‘\GS\201\SO%D" Card 16 "\233\ETX\SOHNP\\" Card (-15) "\233n2\183U@y$}\254" Card (-1) "\218m\RSl\a\132\180" Card (-44) ";\138zTL" Card 75 "4\150\178t0oK\193\EM\156Q\ESCk\ny" Card 58 "\157xxU\ACKN\STXK\141\GS\GS\217VV\EOT" [Tho11], Ch. 19 D. Rösner FP 2014 . . . 22 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Datenstrukturen Beispiel: data Info = Number Int | Email String deriving (Eq,Show) instance Arbitrary Info where arbitrary = do boo <- arbitrary if boo then do int <- arbitrary return (Number int) else do string <- arbitrary return (Email string) [Tho11], Ch. 19 D. Rösner FP 2014 . . . 23 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Datenstrukturen Beispiel: *Main> sample (arbitrary :: Gen Info) Email "" Email "<\154" Email "zE\190R" Email "\tG\\\ETB\NUL" Number (-6) Number 9 Email "D\173;d" Email "" Email "m\EOTw" Number 44 Email "BOs \180\131L\SOHy)" *Main> sample (arbitrary :: Gen Info) Email "" Number 1 Email "M" Email "<\173\DELRk8" Email "P\EOT\150)" Number (-16) Email "(\243\179iR!F\189" Number (-3) Email "\188a" Number 46 Number 109 D. Rösner FP 2014 . . . 24 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Datenstrukturen Beispiel: data List a = Empty | Cons a (List a) deriving (Eq,Show) instance Arbitrary a => Arbitrary (List a) where arbitrary = do boo <- arbitrary if boo then return Empty else do a1 <- arbitrary l1 <- arbitrary return (Cons a1 l1) [Tho11], Ch. 19 D. Rösner FP 2014 . . . 25 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Datenstrukturen Beispiel: *Main> sample (arbitrary :: Gen (List Int)) Empty Cons (-1) (Cons (-1) (Cons 1 Empty)) Cons (-1) Empty Empty Cons 8 (Cons (-6) (Cons 2 (Cons 2 Empty))) Empty Empty Cons 22 Empty Empty Cons (-74) Empty Empty *Main> sample (arbitrary :: Gen (List String)) Empty Cons "" (Cons "" Empty) Empty Empty Cons "" Empty Cons "k\\~\ak\202\SIf" Empty Empty Cons "M\166\180\142\231?lmP[H=" (Cons "\128\DC3," Empty) Empty Empty Empty D. Rösner FP 2014 . . . 26 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Rekursive Datenstrukturen Beispiel: data Expr = Lit Integer | Add Expr Expr | Sub Expr Expr deriving (Eq,Show) instance Arbitrary Expr where arbitrary = sized arbExpr *Main> :t sized sized :: (Int -> Gen a) -> Gen a [Tho11], Ch. 19 D. Rösner FP 2014 . . . 27 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Rekursive Datenstrukturen Implementierung 1 arbExpr 0 = do int <- arbitrary return (Lit int) arbExpr n | n>0 = do pick <- choose (0,2::Int) case pick of 0 -> do int <- arbitrary return (Lit int) 1 -> do left <- subExp right <- subExp return (Add left right) 2 -> do left <- subExp right <- subExp return (Sub left right) where subExp = arbExpr (div n 2) [Tho11], Ch. 19 D. Rösner FP 2014 . . . 28 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Rekursive Datenstrukturen Implementierung 2 {- reimplementation with frequency and liftM (from Control.Monad) arbExpr 0 = liftM Lit arbitrary arbExpr [(1, (2, (2, n = frequency liftM Lit arbitrary), liftM2 Add subExp subExp), liftM2 Sub subExp subExp)] where subExp = arbExpr (div n 2) Control.Monad: *Main> :t liftM liftM :: Monad m => (a1 -> r) -> m a1 -> m r *Main> :t liftM2 liftM2 :: Monad m => (a1 -> a2 -> r) -> m a1 -> m a2 -> m r [Tho11], Ch. 19 D. Rösner FP 2014 . . . 29 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Rekursive Datenstrukturen *Main> sample (arbitrary :: Gen Expr) Lit 0 Sub (Add (Lit 2) (Lit 0)) (Sub (Lit 0) (Lit (-1))) Sub (Lit 2) (Sub (Lit (-4)) (Add (Lit (-2)) (Lit 0))) Add (Sub (Sub (Lit 5) (Lit (-1))) (Add (Lit 2) (Lit (-2)))) (Sub (Lit (-6)) (Add (Lit 5) (Lit (-5)))) Lit 2 Add (Sub (Lit (-7)) (Sub (Sub (Lit 7) (Lit (-9))) (Lit (-3)))) (Sub (Lit (-4)) (Sub (Lit 8) (Sub (Lit 6) (Lit 7)))) Sub (Lit 3) (Add (Add (Lit 8) (Lit 0)) (Lit 10)) Lit (-10) Lit 11 Sub (Sub (Add (Lit 8) (Sub (Add (Lit (-4)) (Lit 11)) (Sub (Lit 11) (Lit (-3))))) (Lit (-13))) (Add (Add (Add (Add (Lit 14) (Lit (-7))) (Sub (Lit (-7)) (Lit (-14)))) (Add (Sub (Lit (-2)) (Lit (-11))) (Add (Lit (-11)) (Lit (-9))))) (Lit (-10))) Add (Sub (Add (Sub (Sub (Lit 3) (Lit (-15))) (Add (Lit (-11)) (Lit 16))) (Lit (-7))) (Sub (Add (Sub (Lit (-10)) (Lit (-6))) (Sub (Lit (-2)) (Lit 20))) (Lit (-11)))) (Sub (Lit 10) (Lit (-1))) [Tho11], Ch. 19 D. Rösner FP 2014 . . . 30 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Eigenschaften von Funktionen Beispiel: prop_map_1 f g xs = map (f::Int->Int) (map (g::Int->Int) xs) == map (f.g) xs Beispiel: prop_map_2 f g xs = map (f::Int->Int) (map (g::Int->Int) xs) == map (g.f) xs Welche ist zutreffend? [Tho11], Ch. 19 D. Rösner FP 2014 . . . 32 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Eigenschaften von Funktionen Beispiel: *Main> quickCheck prop_map_1 +++ OK, passed 100 tests. Beispiel: *Main> quickCheck prop_map_2 *** Failed! Falsifiable (after 3 tests and 2 shrinks): (1|->0) ,(-1|->1) ,(0|->1) ,(-2|->1) ,(-3|->0) ,(-1|->1) ,(11|->-1) ,(22|->-1) ,(15|->1) ,(-28|->-1) ,(-202|->-1) (0|->-1) ,(-1|->1) ,(-2|->1) ,(-3|->1) ,(5|->0) ,(-9|->0) ,(-3|->1) ,(-10|->1) ,(36|->0) ,(20|->0) ,(-197|->-1) [0] Interpretation: zunächst die Abbildungsvorschrift von f, dann die von g auf Testliste [0] gilt Behauptung nicht f 0 = 1, g 1 = 0 (Default); aber: g 0 = -1, f (-1) = 1 [Tho11], Ch. 19 D. Rösner FP 2014 . . . 33 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Eigenschaften von Funktionen *Main> verboseCheck prop_map_2 Passed: (0|->-1) ,(1|->-1) ,(-2|->0) ,(3|->0) ,(8|->1) ,(2|->0) ,(-14|->0) ,(-15|->0) ,(39|->0) ,(-10|->1) ,(170|->1) (1|->-1) ,(-1|->0) ,(1|->-1) ,(-1|->0) ,(5|->1) ,(-12|->0) ,(2|->-1) ,(-19|->0) ,(-32|->-1) ,(-125|->0) ,(22|->1) [] Passed: ... Failed: (0|->0) ,(0|->0) ,(0|->0) ,(1|->1) ,(-7|->0) ,(8|->1) ,(-1|->0) ,(12|->-1) ,(-28|->2) ,(-114|->-1) ,(-105|->2) (0|->-2) ,(0|->-2) ,(-1|->-2) ,(2|->-1) ,(7|->-1) ,(5|->2) ,(9|->-2) ,(8|->-1) ,(-14|->2) ,(6|->-2) ,(-197|->2) [0,-1,-1,1] *** Failed! Passed: ... Failed: ... Falsifiable (after 5 tests and 2 shrinks): (0|->0) ,(0|->0) ,(0|->0) ,(1|->1) ,(-7|->0) ,(8|->1) ,(-1|->0) ,(12|->-1) ,(-28|->2) ,(-114|->-1) ,(-105|->2) (0|->-2) ,(0|->-2) ,(-1|->-2) ,(2|->-1) ,(7|->-1) ,(5|->2) ,(9|->-2) ,(8|->-1) ,(-14|->2) ,(6|->-2) ,(-197|->2) [1] D. Rösner FP 2014 . . . 34 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte Fallstudie: QuickCheck zentral sind Generatoren für Testdaten Typ: newtype Gen a = Gen (Int -> StdGen -> a) Typ Gen ist Funktor und Monade D. Rösner FP 2014 . . . 36 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte Fallstudie: QuickCheck Definition als Funktor: instance Functor Gen where fmap f m = m >>= return . f Definition als Monade: instance Monad Gen where return a = Gen (\n r -> a) Gen m >>= k = Gen (\n r0 -> let (r1,r2) = split r0 Gen m’ = k (m n r1) in m’ n r2) D. Rösner FP 2014 . . . 37 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte Fallstudie: QuickCheck zentrale Funktion: choose :: Random a => (a, a) -> Gen a choose bounds = (fst . randomR bounds) ‘fmap‘ rand rand :: Gen StdGen rand = Gen (\n r -> r) D. Rösner FP 2014 . . . 38 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte Fallstudie: QuickCheck Kombinatoren für Generatoren: Auswahl aus einer Liste von Generatoren oneof :: [Gen a] -> Gen a oneof gens = elements gens >>= id Beispiel: oneof [return True, return False] D. Rösner FP 2014 . . . 39 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte Fallstudie: QuickCheck Kombinatoren für Generatoren: Beeinflussung der Verteilung: frequency :: [(Int, Gen a)] -> Gen a frequency xs = choose (1, tot) >>= (‘pick‘ xs) where tot = sum (map fst xs) pick n ((k,x):xs) | n <= k = x | otherwise = pick (n-k) xs Beispiel: frequency [(2,return True), (1,return False)] D. Rösner FP 2014 . . . 40 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte Fallstudie: QuickCheck vordefinierte Generatoren für zahlreiche Typen Überladen der Methoden der Klasse Arbitrary class Arbitrary a where arbitrary :: Gen a coarbitrary :: a -> Gen b -> Gen b arbitrary . . . für zufällige Werte des Typs a coarbitrary . . . für zufällige Funktionen des Typs a-> b D. Rösner FP 2014 . . . 41 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte Fallstudie: QuickCheck vordefinierte Generatoren für zahlreiche Typen Beispiel: Typ Arbitrary Int instance Arbitrary Int where arbitrary = sized $ \n -> choose (-n,n) coarbitrary n = variant (if n >= 0 then 2*n else 2*(-n) + 1) D. Rösner FP 2014 . . . 42 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte Fallstudie: QuickCheck vordefinierte Generatoren für zahlreiche Typen Beispiel: Typ Arbitrary (a, b): instance (Arbitrary a, Arbitrary b) => Arbitrary (a, b) where arbitrary = liftM2 (,) arbitrary arbitrary coarbitrary (a, b) = coarbitrary a . coarbitrary b verwendet wird hier die monadische Funktion liftM2 liftM2 :: (Monad m) => (a -> b -> c) -> (m a -> m b -> m c) liftM2 f = \a b -> do { a’ <- a; b’ <- b; return (f a’ b’) } D. Rösner FP 2014 . . . 43 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Implementation Kombinatoren, die Generatoren produzieren: elements . . . erzeuge zu einem aus einer Liste zufällig ausgewählten Element einen Generator elements :: [a] -> Gen a elements xs = (xs !!) ‘fmap‘ choose (0, length xs - 1) D. Rösner FP 2014 . . . 45 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Implementation Beispiele: *Main> sample (elements [’a’..’f’]) ’c’ ’d’ ’f’ ’d’ ’f’ ’d’ ’d’ ’a’ ’e’ ’e’ ’b’ D. Rösner FP 2014 . . . 46 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Implementation Beispiele: *Main> sample (choose (’a’,’f’)) ’e’ ’c’ ’d’ ’f’ ’a’ ’a’ ’f’ ’b’ ’d’ ’d’ ’c’ D. Rösner FP 2014 . . . 47 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Implementation Kombinatoren, die Generatoren produzieren: vector . . . produziere einen Generator für Listen mit n zufälligen Elementen vector :: Arbitrary a => Int -> Gen [a] vector n = sequence [ arbitrary | i <- [1..n] ] D. Rösner FP 2014 . . . 48 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Implementation die Definition von vector verwendet die allgemeine monadische Funktion sequence, die aus einer Liste von Monadeninstanzen eine Monadeninstanz für eine Liste produziert im Prelude.hs: sequence :: Monad m => [m a] -> m [a] sequence [] = return [] sequence (c:cs) = do x <- c xs <- sequence cs return (x:xs) D. Rösner FP 2014 . . . 49 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Implementation variant . . . Kombinator, um zu Generator gezielt Varianten erzeugen zu können variant :: Int -> Gen a -> Gen a variant v (Gen m) = Gen (\n r -> m n (rands r !! (v+1))) where rands r0 = r1 : rands r2 where (r1, r2) = split r0 durch wiederholtes Anwenden von split wird aus dem übergebenen Zufallsgenerator eine Liste von Zufallsgeneratoren erzeugt, aus der dann das (v+1)-te Element entnommen und dann im Testgenerator verwendet wird D. Rösner FP 2014 . . . 50 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: weitere Aspekte der Implementation Variation der Standard-Parameter *Main> :t quickCheckWith quickCheckWith :: Testable prop => Args -> prop -> IO () *Main> quickCheckWith stdArgs { maxSuccess = *** Failed! Falsifiable (after 110 tests): [-3,-3,-3] *Main> quickCheckWith stdArgs { maxSuccess = *** Failed! Falsifiable (after 607 tests and [4,4,4] *Main> quickCheckWith stdArgs { maxSuccess = *** Failed! Falsifiable (after 408 tests and [-3,-3,-3] 5000 } prop_RemDup4’’ 5000 } prop_RemDup4’’ 1 shrink): 5000 } prop_RemDup4’’ 1 shrink): fehlerhafte Implementierung: remDuplicates [] = [] remDuplicates [x] = [x] remDuplicates (x:y:xs) = if x == y then y:remDuplicates xs else x : remDuplicates (y:xs) D. Rösner FP 2014 . . . 52 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: weitere Aspekte der Implementation direkte Implementierung: remDuplicates [] = [] remDuplicates (x:xs) = if (elem x xs) then remDuplicates xs else x : remDuplicates xs *Main> quickCheckWith stdArgs { maxSuccess = 5000 } prop_RemDup4’’ +++ OK, passed 5000 tests: 80% three or more elems, nontrivially all different 5% three or more elems 5% empty list 4% singleton 3% pair list D. Rösner FP 2014 . . . 53 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: der Datentyp Result Definition: data Result = Result { ok :: Maybe Bool, stamp :: [String], arguments :: [String] } Beispiel: nothing :: Result nothing = Result{ ok = Nothing, stamp = [], arguments = [] } D. Rösner FP 2014 . . . 54 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Repräsentation testbarer Prädikate Typ Property newtype Property = Prop (Gen Result) Klasse Testable class Testable a where property :: a -> Property D. Rösner FP 2014 . . . 55 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck: Repräsentation testbarer Prädikate zugehörige Funktionen: result :: Result -> Property result res = Prop (return res) evaluate :: Testable a => a -> Gen Result evaluate a = gen where Prop gen = property a D. Rösner FP 2014 . . . 56 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck Elemente der Sprache für testbare Spezifikationen forAll forAll :: (Show a, Testable b) => Gen a -> (a -> b) -> Property forAll gen body = Prop $ do a <- gen res <- evaluate (body a) return (argument a res) where argument a res = res{ arguments = show a : arguments res } D. Rösner FP 2014 . . . 57 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck Elemente der Sprache für testbare Spezifikationen (==>) (==>) :: Testable a => Bool -> a -> Property True ==> a = property a False ==> a = property () label label :: Testable a => String -> a -> Property label s a = Prop (add ‘fmap‘ evaluate a) where add res = res{ stamp = s : stamp res } D. Rösner FP 2014 . . . 58 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte QuickCheck Elemente der Sprache für testbare Spezifikationen classify classify :: Testable a => Bool -> String -> a -> Property classify True name = label name classify False _ = property trivial trivial :: Testable a => Bool -> a -> Property trivial = (‘classify‘ "trivial") D. Rösner FP 2014 . . . 59 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte Literatur: I Koen Claessen and John Hughes. Quickcheck: a lightweight tool for random testing of haskell programs. In Martin Odersky and Philip Wadler, editors, ICFP, pages 268–279. ACM, 2000. Bryan O’Sullivan, John Goerzen, and Don Stewart. Real World Haskell. O’Reilly Media, Sebastopol, CA 95472, 2009. ISBN 978-0-596-51498-3; online available at http://book.realworldhaskell.org/. D. Rösner FP 2014 . . . 60 Quickcheck Einführung Testen Datenstrukturen Funktionen Generatoren Implementation Weitere Aspekte Literatur: II Simon Thompson. Haskell - The Craft of Functional Programming. Addison Wesley Longman Ltd., Essex, 1999. 2nd edition, ISBN 0-201-34275-8; Accompanying Web site: http://www.cs.ukc.ac.uk/people/staff/sjt/craft2e. Simon Thompson. Haskell - the craft of functional programming. Pearson Education Ltd., Essex, 2011. 3rd edition, ISBN 978-0-201-88295-7; Accompanying Web site: http://www.haskellcraft.com. D. Rösner FP 2014 . . . 61