Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Abstract state machine testing #110

Open
moodmosaic opened this issue Jul 4, 2017 · 5 comments
Open

Abstract state machine testing #110

moodmosaic opened this issue Jul 4, 2017 · 5 comments

Comments

@moodmosaic
Copy link
Member

moodmosaic commented Jul 4, 2017

The F# version should also support State Machine Testing similar to the Haskell version.

See hedgehogqa/haskell-hedgehog#89, and at least up to hedgehogqa/haskell-hedgehog#96.

To have a solid foundation to build on, we first need to have Tree and Gen data types and combinators in line with the Haskell version, as per #111 and #89.

@moodmosaic
Copy link
Member Author

[..] need to have Tree and Gen data types and combinators in line with the Haskell version.

For Gen, look at the discussion in #89.

@moodmosaic
Copy link
Member Author

Fun experiment: https://github.com/moodmosaic/hedgehog-inline-csharp-testing

Example of using haskell-hedgehog's model-based state machine testing together with clr-inline (C#).

@moodmosaic
Copy link
Member Author

We should just do this the 'modern' way by having Hedgehog construct a random structure by executing generated commands, as shown in https://github.com/moodmosaic/hedgehog-stateful-app-testing/blob/master/test/test.hs

A simplified version of the above in F# would be to have Hedgehog generate a list of commands which you then apply into both the system under test and the state representing the model, and then you compare the two.

open System.Collections.Generic

open Hedgehog
open Swensen.Unquote

[<Struct>]
type UserId = 
    | UserId of string

type User = 
    { id : UserId
      name : string
      age : int }

type IUserOperations = 
    abstract AddUser : User   -> unit
    abstract GetUser : UserId -> Option<User>
    abstract DelUser : UserId -> unit

type UserSystem () = 
    let users =
        Dictionary<UserId, User> ()

    member _.UserCount = users.Count

    interface IUserOperations with
        member _.AddUser u =
            users.[u.id] <- u
        member _.GetUser id = 
            match users.TryGetValue id with
            | (true, user) -> Some user
            |            _ -> None
        member _.DelUser (UserId id) = 
            if id.Contains "*" // Uh-oh: catastrophic bug!
            then
                users.Clear ()
            else
                users.Remove (UserId id) |> ignore

type Operation = 
    | Add of User
    | Get of UserId
    | Del of UserId

let applyOp (op : Operation) (handler : IUserOperations) = 
    match op with
    | Add user -> handler.AddUser user
    | Get name -> handler.GetUser name |> ignore
    | Del name -> handler.DelUser name

type UserCountModel () = 
    let expected =
        HashSet<UserId> ()
    member _.Verify (actual : UserSystem) =
        expected.Count =! actual.UserCount
    interface IUserOperations with
        member _.AddUser user = expected.Add user.id |> ignore
        member _.DelUser name = expected.Remove name |> ignore
        member _.GetUser name = None

[<EntryPoint>]
let main argv =
    Property.print <| property {
        let state = UserCountModel ()
        let model = UserSystem ()
        let! operations = GenX.auto<Operation list>
        operations |> List.iter (fun op -> applyOp op model; applyOp op state)
        state.Verify model
    }
    0 // Return an integer exit code.

@ghost
Copy link

ghost commented Sep 21, 2021

@moodmosaic what would we need to implement from where we are now to get this done?

@moodmosaic
Copy link
Member Author

Now, in 2021, I would try to avoid adding a dedicated API for state-machine testing.

Instead, I would add the example code above in the README/docs, or in its own test-suite, and be done with it.

As @jacobstanley also describes in his post:

"The state-machine testing libraries aimed at solving this problem use complicated types that are overwhelming and hard to use, especially for newbies."

This has been true also IME, in various shops/groups I've helped with PBT in the past.


Perhaps, an even better example/scenario would be to port Jake's post in F# (or C#) Hedgehog and add also that in the README/docs, or in a test-suite, and port any bits missing from Haskell in order to achieve that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant