Skip to content

Instantly share code, notes, and snippets.

@TheSeamau5
Last active August 29, 2015 14:16
Show Gist options
  • Save TheSeamau5/3855e1130cd66fc3c6b3 to your computer and use it in GitHub Desktop.
Save TheSeamau5/3855e1130cd66fc3c6b3 to your computer and use it in GitHub Desktop.
API for testing stateful code in Elm
{-
This is an example of how to test state in Elm.
The idea is that one can model your application as a state machine
with an update function of the type:
update : action -> state -> state
This is very useful in order to test applications
built using the Elm architecture, which looks like this:
initialState : state
view : state -> effect
update : action -> state -> state
actions : Signal action
main : Signal effect
main =
view <~ foldp update initialState actions
The following examples focuses on the `update` part
of the Elm architecture through the example of stacks.
-}
-- We model a stack as a list
type alias Stack a = List a
-- A stack has two operations: push and pop
push : a -> Stack a -> Stack a
pop : Stack a -> (Maybe a, Stack a)
-- We model these operations as actions
type Action a
= Push a
| Pop
-- The update function will take an action
-- and update the stack by calling the appropriate operation
update : Action a -> Stack a -> Stack a
update action stack = case action of
Push a -> push a stack
Pop -> snd (pop stack)
-- Then we turn each action into a command for elm-check to generate.
-- `command` takes:
-- * the name of the command (for printing)
-- * a generator of the given action
-- You can then add options to the command. These options are:
-- * preconditions : A list of conditions that specify which actions to be
-- ignored given a state.
-- * transition : The transition function that will transition from
-- one state to the other
-- * postconditions : A list of conditions that should be true of the new state
-- with respect to the old state after the action was run
post_pop_diffLength : PostCondtion (Stack a)
post_pop_diffLength = condition "Diff Length"
\oldStack newStack ->
let
diffLength = length oldStack - length newStack
in
diffLength == 1 || diffLength == 0 && (length oldStack) == 0
-- Pop command
command_pop : Command (Action a) (Stack a)
command_pop = command "Pop" (constant Pop)
`transition` update
`postconditions`
[ post_pop_diffLength ]
post_push_stackLengthIncrementedBy1 : PostCondition (Stack a)
post_push_stackLengthIncrementedBy1 = condition "Stack Length Incremented By 1"
\oldStack newStack ->
length newStack - length oldStack == 1
-- Push command
command_push : Command (Action Int) (Stack Int)
command_push = command "Push" (map Push (int 0 100))
`transition` update
`postconditions`
[ post_push_stackLengthIncrementedBy1 ]
-- Sample generator of stack (generates a stack of ints)
generator_stack : Generator (Stack Int)
generator_stack = rangeLengthList 0 100 (int 0 100)
-- We then make a property out of these commands using `commands`
-- `commands` takes :
-- * the name of the property (again, for printing)
-- * the generator for the state (in this case the stack generator)
-- * the list of commands
spec_stack : Property
spec_stack =
commands "Stack" generator_stack
[ command_pop
, command_push
]
type alias Shrinker a = a -> List a
type alias Command action state =
Generator state -> CommandOptions action state
type alias PreCondition action state =
{ name : String
, condition : action -> state -> Bool
}
type alias Transition action state = action -> state -> state
type alias PostCondition state =
{ name : String
, condition : state -> state -> Bool
}
condition : String -> condition -> { name : String, condition : condition }
condition name cond =
{ name = name
, condition = cond
}
type alias CommandOptions action state =
{ name : String
, preconditions : List (PreCondition action state)
, transition : Transition action state
, postconditions : List (PostCondition state)
, generator : Generator action
, shrinker : Maybe (Shrinker action)
}
command : String -> Generator action -> Command action state
command name actionGenerator =
\stateGenerator ->
{ name = name
, preconditions = []
, transition = \action state -> state
, postconditions = []
, generator = actionGenerator
, shrinker = Nothing
}
preconditions : Command action state -> List (PreCondition action state) -> Command action state
preconditions stateToCommand conditions =
\stateGenerator ->
let
command = stateToCommand stateGenerator
in
{ command | preconditions <- conditions }
transition : Command action state -> Transition action state -> Command action state
transition stateToCommand updater =
\stateGenerator ->
let
command = stateToCommand stateGenerator
in
{ command | transition <- updater }
postconditions : Command action state -> List (PostCondition state) -> Command action state
postconditions stateToCommand conditions =
\stateGenerator ->
let
command = stateToCommand stateGenerator
in
{ command | postconditions <- conditions }
shrinkWith : Command action state -> Shrinker action -> Command action state
shrinkWith stateToCommand shrinker =
\stateGenerator ->
let
command = stateToCommand stateGenerator
in
{ command | shrinker <- shrinker }
commands : String -> Generator state -> List (Command action state) -> Property
commands = -- And then a miracle occurs :D http://star.psy.ohio-state.edu/coglab/Miracle.html
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment