Course for learning how to apply property-based state-machine testing
Haskell
62
93 commits
updated Sep 13, 2019
This is in progress course material for a course on property-based state machine testing, using the Hedgehog package.
State Machine Testing extends property-based testing to provide a toolkit for building randomised tests of stateful systems. Like property-based testing, state machine tests use random generators to create test cases and shrink failing tests to minimal counter-examples. The difference with state machine testing is that the random input is now a sequence of commands to perform instead of arguments to pure functions.
In this course, we'll be using
hedgehog's state machine
testing. The QuickCheck ecosystem has its own
quickcheck-state-machine
package which we won't cover.
How do we know that our stateful system is behaving itself? We build a model of the system being tested, and use it in a few ways:
Not all actions make sense at all times (e.g., what should happen if you try to log in when you're already logged-in?). When hedgehog generates a command sequence, we update the model being tested and use it to limit the actions we generate.
When we run tests, we perform commands both on the model and the system being tested, and check that their results agree.
The system we're testing is a vending machine for hot drinks, defined
in src/CoffeeMachine.hs. You can select which drink you'd like,
insert or remove a mug, add milk or sugar, insert coins and dispense a
beverage. We'll be testing these features at different levels of the
course.
The course itself is broken apart into several levels. Because this is
a course about testing, each level is a separate test-suite in the
.cabal file, and its own directory.
Solutions are on the solutions branch, one commit per level.
ghcidghcid is a helpful tool that
helps to automate the 'edit-save-build' workflow. ghcid can also
execute code or perform other actions after a successful build. In
this case we're going to setup ghcid to run our tests whenever our
code is in a buildable state.
dev.ghci script in the root of this project to prepare our repl
and load the required modules::set -isrc:levelNN
:load levelNN/Main.hs
The first line indicates which folders ghci will include when looking for any
of our code. We can provide multiple directories by providing a colon (:)
separated list.
The second line loads the module that contains the function we want to execute
when the code is buildable. In this case it is main :: IO () function that
runs our tests.
ghcid to use our dev.ghci and also what command to run when
everything is 'All good'. Additionally we will instruct ghcid to ignore
warnings:$ ghcid -c 'ghci -ghci-script="dev.ghci"' --test=:main -W
We use ghci instead of cabal new-repl so we can provide the dev.ghci to
setup our repl environment. The --test=:main is the repl command that will be
executed. Finally -W tells ghcid to ignore any warnings from compilation.
You will need to run a few additional commands when using stack:
# Initialise
$ stack init
# Replace `level01` with the level you are working on
$ stack test :level01
# REPL, if you want it
$ stack ghci :level01
Command structureRequire & Pre-conditionsHaskell
75.9%
HTML
21.1%
Nix
2.2%
Course for learning how to apply property-based state-machine testing
Haskell
62
93 commits
updated Sep 13, 2019
This is in progress course material for a course on property-based state machine testing, using the Hedgehog package.
State Machine Testing extends property-based testing to provide a toolkit for building randomised tests of stateful systems. Like property-based testing, state machine tests use random generators to create test cases and shrink failing tests to minimal counter-examples. The difference with state machine testing is that the random input is now a sequence of commands to perform instead of arguments to pure functions.
In this course, we'll be using
hedgehog's state machine
testing. The QuickCheck ecosystem has its own
quickcheck-state-machine
package which we won't cover.
How do we know that our stateful system is behaving itself? We build a model of the system being tested, and use it in a few ways:
Not all actions make sense at all times (e.g., what should happen if you try to log in when you're already logged-in?). When hedgehog generates a command sequence, we update the model being tested and use it to limit the actions we generate.
When we run tests, we perform commands both on the model and the system being tested, and check that their results agree.
The system we're testing is a vending machine for hot drinks, defined
in src/CoffeeMachine.hs. You can select which drink you'd like,
insert or remove a mug, add milk or sugar, insert coins and dispense a
beverage. We'll be testing these features at different levels of the
course.
The course itself is broken apart into several levels. Because this is
a course about testing, each level is a separate test-suite in the
.cabal file, and its own directory.
Solutions are on the solutions branch, one commit per level.
ghcidghcid is a helpful tool that
helps to automate the 'edit-save-build' workflow. ghcid can also
execute code or perform other actions after a successful build. In
this case we're going to setup ghcid to run our tests whenever our
code is in a buildable state.
dev.ghci script in the root of this project to prepare our repl
and load the required modules::set -isrc:levelNN
:load levelNN/Main.hs
The first line indicates which folders ghci will include when looking for any
of our code. We can provide multiple directories by providing a colon (:)
separated list.
The second line loads the module that contains the function we want to execute
when the code is buildable. In this case it is main :: IO () function that
runs our tests.
ghcid to use our dev.ghci and also what command to run when
everything is 'All good'. Additionally we will instruct ghcid to ignore
warnings:$ ghcid -c 'ghci -ghci-script="dev.ghci"' --test=:main -W
We use ghci instead of cabal new-repl so we can provide the dev.ghci to
setup our repl environment. The --test=:main is the repl command that will be
executed. Finally -W tells ghcid to ignore any warnings from compilation.
You will need to run a few additional commands when using stack:
# Initialise
$ stack init
# Replace `level01` with the level you are working on
$ stack test :level01
# REPL, if you want it
$ stack ghci :level01
Command structureRequire & Pre-conditionsHaskell
75.9%
HTML
21.1%
Nix
2.2%