-- | Test Data Generators -- Examples to introduce QuickCheck test data generators -- Functional Programming course 2017. -- Thomas Hallgren {- This started as a skeleton, the definitions were filled in during the lecture. -} module TestDataGenerators where import Data.List(nub) import Test.QuickCheck import Overloading hiding ((==>)) -- imports Suit, Rank, Card, Hand, etc -------------------------------------------------------------------------------- -- * Test data generator for your own data types -- (using the data types for playing cards from week 1) --data Suit = Spades | Hearts | Diamonds | Clubs rSuit :: Gen Suit rSuit = elements [Spades,Hearts, Diamonds, Clubs] --data Rank = Numeric Int | Jack | Queen | King | Ace --all_ranks = [Numeric n|n<-[2..10]]++[Jack,Queen,King,Ace] rRank_v1 :: Gen Rank rRank_v1 = elements all_ranks -- ** A more structured approach to generating ranks rNumeric :: Gen Rank rNumeric = elements [Numeric n|n<-[2..10]] rFaceCard :: Gen Rank rFaceCard = elements [Jack,Queen,King,Ace] rRank_v2 :: Gen Rank rRank_v2 = oneof [rNumeric,rFaceCard] rRank_v3 :: Gen Rank rRank_v3 = frequency [(9,rNumeric),(4,rFaceCard)] rRank = rRank_v3 --data Card = Card Rank Suit rCard :: Gen Card rCard = do r <- rRank s <- rSuit return (Card r s) -- We have: -- rSuit :: Gen Suit -- rRank :: Gen Rank -- Card :: Rank -> Suit -> Card -- We want: -- rCard :: Gen Card -- Not in the library, since the do notation is more flexible -- genPair :: Gen a -> Gen b -> Gen (a,b) -------------------------------------------------------------------------------- -- * More examples of generators defined using do and return evenInteger :: Gen Integer --evenInteger = do n <- arbitrary -- return (2*n) evenInteger = (2*) <\$> arbitrary nonNegative :: Gen Integer nonNegative = abs <\$> arbitrary pairsOfEvenIntegers = doTwice evenInteger doTwice io = do x <- io y <- io return (x,y) --data Hand = Empty | Add Card Hand -- | Generating random hands rHand_v1 :: Gen Hand rHand_v1 = oneof [return Empty, do c <- rCard h <- rHand_v1 return (Add c h)] -- | Changing the distribution to get more big hands rHand_v2 :: Gen Hand rHand_v2 = frequency [(1,return Empty), (4,do c <- rCard h <- rHand_v2 return (Add c h))] -- | Generating a hand from a list of cards, eliminating duplicated cards rHand_v3 :: Gen Hand rHand_v3 = undefined -- left as an exercise. Hint: use arbitrary & nub listToHand :: [Card] -> Hand listToHand = undefined -- left as an exercise. Hint: use foldr rHand = rHand_v2 -------------------------------------------------------------------------------- -- * Making instances in the Arbitrary class to specify default test -- data generators instance Arbitrary Suit where arbitrary = rSuit instance Arbitrary Rank where arbitrary = rRank instance Arbitrary Card where arbitrary = rCard instance Arbitrary Hand where arbitrary = rHand -------------------------------------------------------------------------------- -- * Testing test data generators -- | Not all values of type Rank are valid ranks. validRank (Numeric n) = 2<=n && n<=10 validRank _ = True -- | Specifying a test data generator explicitly (using forAll), -- testing that rRank only generates valid ranks. prop_all_validRank_1 = forAll rRank validRank -- | Tesing that 'arbitrary' only generates valid ranks prop_all_validRank_2 r = validRank r -- | Examining test data distribution (using collect) prop_all_validRank_3 :: Rank -> Property prop_all_validRank_3 r = collect r True -- | Examining the distribution of sizes of hands generated by 'arbitrary' prop_Hand h = collect (size h) True size Empty = 0 size (Add c h) = 1+size h -------------------------------------------------------------------------------- -- * Testing properties of algorithms -- | Insert a new element at the right position in a sorted list insert :: Ord a => a -> [a] -> [a] insert x (y:ys) | x>y = y:insert x ys insert x ys = x:ys -- | Testing if a list is ordered isOrdered :: Ord a => [a] -> Bool isOrdered (x1:x2:xs) = x1<=x2 && isOrdered (x2:xs) isOrdered _ = True -- | Testing insert, first attempt prop_insert_1 :: Int -> [Int] -> Bool prop_insert_1 x xs = isOrdered (insert x xs) -- | Testing insert, second attempt prop_insert_2 :: Int -> [Int] -> Property prop_insert_2 x xs = isOrdered xs ==> isOrdered (insert x xs) -- | How many of randomly generated the lists are ordered? prop_isOrdered xs = collect (isOrdered (xs::[Int])) True -- ** Testing with ordered lists -- | Using generator orderedList prop_insert_3 x = forAll orderedList (\xs->isOrdered (insert x (xs::[Int]))) -- | Using the type Ordered prop_insert_4 x (Ordered xs) = isOrdered (insert x (xs::[Int]))