but there are useful tools. To write testable code, it’s much like any other language: • Aim for small functions • Minimize dependencies/coupling • Avoid IO/side-effects • etc
– but there are useful tools. In order of preference: 1. Use the type system to enforce correctness 2. Use libraries to generate tests for us (QuickCheck) 3. Write tests ourselves
minimize things that need to be tested, make it harder to write incorrect code. Two basic things: • Encode invariants in types • Prefer more general types
does not export constructor. “Name(..)” would -- A Name is a non-empty String. newtype Name = Name String deriving (Show, Eq, Ord) -- “Smart constructor” toName :: String → Maybe Name toName "" = Nothing toName n = Just (Name n) lookupPerson :: Name → IO (Maybe Person)
Name → Name → Bool getIdIsDistinct n1 n2 = (n1 == n2) == (getId n1 == getId n2) -- Test the property: > quickCheck getIdIsDistinct No instance for (Arbitrary Name) arising from a use of `quickCheck'
Gen a choose :: Random a => (a, a) → Gen a -- Generates a random element in the given inclusive range. elements :: [a] → Gen a -- Generates one of the given values. frequency :: [(Int, Gen a)] → Gen a -- Chooses one of the given generators, with a weighted random distribution.
[String] findLines word filePath = do text ← readFile filePath return $ filter containsWord $ lines text where containsWord line = word `isInfixOf` line
[String] findLines word filePath = do text ← readFile filePath return $ filter containsWord $ lines text where containsWord line = word `isInfixOf` line
FilePath → m [String] findLines word filePath = do text ← readText filePath return $ filter containsWord $ lines text where containsWord line = word `isInfixOf` line
instance RWMonad InMemory where readText fileName = do files ← get case Map.lookup fileName files of Nothing → error ("File not found: " ++ fileName) Just content → return content writeText fileName content = do files ← get put (Map.insert fileName content files) runInMemory :: InMemory r → r runInMemory f = evalState f Map.empty
directory to work in. Inside the directory: “cabal sandbox init” Then: “cabal install quickcheck” Now you can make your .hs file, then load it with: “cabal repl Test.hs”