Last active
August 29, 2015 14:16
-
-
Save TheSeamau5/3855e1130cd66fc3c6b3 to your computer and use it in GitHub Desktop.
API for testing stateful code in Elm
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{- | |
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 | |
] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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