fsprojects-archive / zzarchive-FSharp.Desktop.UI

F# MVC framework for WPF.
http://fsprojects.github.io/FSharp.Desktop.UI/
Other
81 stars 21 forks source link

Adding tests that mimic eventstreams #11

Closed halfAbee closed 9 years ago

halfAbee commented 9 years ago

I'd like to test the GUI I have built by writing scripts that generate the EventStreams that your mvc controllers dispatch on.

If I have a mousewheel event defined as:

w.MouseWheel |> Observable.map (fun args -> if args.Delta > 0 then Next else Previous)

I'd like to be generating the "Next" or "Previous" events in my integration tests rather than using the real MouseWheel (but still exercising the rest of the real GUI).

I haven't a good idea how to plumb that in; I've tried a few things that didn't really work.

Please would you give a few pointers on how to get started?

Thanks,

George.

dmitry-a-morozov commented 9 years ago

If I understand the question correctly you can use something along these lines:

    type MyEvents = Next | Prev | OtherEvent

    type MyView() =
        inherit View()

        let event = Event<MyEvents>()
        //internal if used only for testing. Don't forget [<assembly:InternalsVisibleTo("MyUnitTestLib")>]
        member internal __.Push event = event.Trigger event

        override this.EventStreams = [ 
            w.MouseWheel |> Observable.map (fun args -> if args.Delta > 0 then Next else Previous) 
            event.Publish            
        ]       

Also, you might consider using Rx subjects instead of Event type from FSharp.Core. Rx implementation has higher quality in terms of performance, memory management and IObservable<_> contract conformance

halfAbee commented 9 years ago

Hi thanks. It was an Rx like approach I was thinking of, since I thought it would be cleaner to be generating streams of "Next" and "Previous" than going in at the GUI event level (e.g. MouseWheel in my example). So my question was really seeking guidance as how to plumb such an EventStream source into your API. From your answer I'm guessing it will be by substituting "event.Publish" with a Rx subject that is generating "Next" and "Previous" events. Which I would pass in as a parameter to "MyView"? My problem is that I've only ever written code to consume streams, not generate them.

dmitry-a-morozov commented 9 years ago

I apologize for really delayed response - I was in "holiday" mode. I covered unit testing a little bit in my series. https://github.com/dmitry-a-morozov/fsharp-wpf-mvc-series/wiki/Controller#unit-testing (only replace EventHandler with Dispacher) Strong separation of concerns is key to this library architecture. Most often you'll be interesting in testing controller functionality. It's completely decoupled from infrastructure components like WPF and RX. Basically you only need invoke Controller.Dispatch myevent mymodel and assert that model properly mutated. There is a little value in integration testing of all 3 MVC components.

There are rare cases when you might want to test a view logic. This will be typical for complex event transformations or data bindings. In this case you can consider manual testing/eyeballing because both binding logic and event transformation are fairly declarative and have little room for repeatable mistakes. Or you can do something like "Window driver" http://martinfowler.com/eaaDev/WindowDriver.html by simulation user action and testing events or data bindings.

halfAbee commented 9 years ago

No problem, it was more an issue of also learning Rx better, which I managed to do.

I agree about unit testing, and it is a great feature of your design. For my use case, I was also looking to automatically driving quite a complicated GUI so that I could eyeball its live behavior; more of an acceptance or "play" testing thing. And I learned how to do it in principle by getting better at Rx inside your current EventStreams list, e.g., at one stage I was stuck thinking how does one ignore some events, until I learned the following idiom:

 w.PreviewKeyDown
      |> Observable.map
        (fun args ->
          match args.Key with
          | Key.R -> Some Random
          | Key.S -> Some Submit
          | _     -> None
        )
      |> Observable.choose id

(where Random and Submitare some events handled by the controller, the point being I don't have to worry about the None case, because of the choose).

My acceptance tests could just be some arbitrarily complex custom EventStreams list. For the moment I just have some extra "high level" or "compound" user level events corresponding to acceptance tests implemented directly via the controller, which served their purpose; I'll be able to do it differently next time.

dmitry-a-morozov commented 9 years ago

Sounds good. To me the easiest way to generate IObservable streams in tests is to use ToObsevable combinator. http://msdn.microsoft.com/en-us/library/hh212140 Also you can define "knobs" on a view to drive testing process. It's similar to "knobs" on StopWatchObservable type I used in chapter 11 of the series. https://github.com/dmitry-a-morozov/fsharp-wpf-mvc-series/wiki/External-Event-Sources Source is here https://github.com/dmitry-a-morozov/fsharp-wpf-mvc-series/blob/master/Chapter%2011%20-%20Composition%20-%20external%20event%20sources/Composition%20-%20external%20event%20sources/StopWatchObservable.fs

BTW, do you think it will be convenient to have pre-defined method Trigger or Push on concrete base View to enable such scenarios?