Intro
I have liked writing automated tests for a long time, so it’s not a surprise that I end up writing them in Haskell too. This is very broad topic, so this episode only scratches the surface.
HSpec
HSpec is testing framework that automatically detects tests, like most of the modern systems. It supports hierarchies, so one can organize tests by feature for example.
spec :: Spec
spec = do
describe "Very important feature" $ do
it "Execution should be error free" $ do
...
it "Flux capacitors can be charged" $ do
...
describe "Somewhat less important feature" $ do
...
Unit test
Unit test tests a single case with fixed set of inputs. With pure functions these are a pleasure to write as they’re really just data in, data out, verify results. Below is two examples:
spec :: Spec
spec = do
describe "Markov chain configuration" $ do
it "Adding new starting element to empty configuration creates item with frequency of 1" $ do
let config = addStart ("AA" :: DT.Text) emptyConfig
config ^? (configStartsL . _head . itemFreqL) `shouldBe` Just 1
config ^? (configStartsL . _head . itemItemL . _Just) `shouldBe` Just "AA"
it "Adding same element twice to empty configuration creates item with frequency of 2" $ do
let config = addStart "AA" $
addStart ("AA" :: DT.Text) emptyConfig
config ^? (configStartsL . _head . itemFreqL) `shouldBe` Just 2
config ^? (configStartsL . _head . itemItemL . _Just) `shouldBe` Just "AA"
Both are for testing configuring markov chains. First one checks that adding a starting element in empty configuration results correct item with correct weight being added. Second checks that adding same starting element twice results weight of 2.
Both tests use lenses for reading nested data structure. Episode doesn’t cover them much at all, as it’s enough to know that (configStartsL . _head . itemFreqL) focuses on starting elements of configuration, selects first item of the list and then selects frequency of that item. Lenses can also be used for modifying data and they don’t have to focus on only one element.
Unit tests are easy enough to write, they verify single thing about the unit being tested and are usually super fast to run and not error prone.
Property based test
Property based tests are used to check that a certain property holds with randomly generated input parameters. I’m using HSpec as testing framework and QuickCheck as tool for generating test data:
spec :: Spec
spec = do
describe "planets" $ do
describe "food" $ do
it "food requirement for positive amount of population is more than zero" $ do
forAll positivePopulation $ x -> foodRequirement x > RawResource 0
it "food base production for farms is equal or greater than their amount" $ do
forAll someFarms $ x -> (sum (fmap foodBaseProduction x)) > (RawResource $ length x)
Above we have two tests. First one checks that with any non-zero population, foodRequirement is greater than 0. Second one check that with any positive amount of farm, foodBaseProduction is greater than amount of the farms.
positivePopulation is Generator, that is used by QuickCheck to generate random data for testing. Its definition is shown below:
singlePopulation :: Gen PlanetPopulation
singlePopulation = do
let aPlanetId = toSqlK