hedgehogqa / fsharp-hedgehog

Release with confidence, state-of-the-art property testing for .NET.
https://hedgehogqa.github.io/fsharp-hedgehog/
Other
272 stars 30 forks source link

Support asynchronous computations: Async and Task #428

Open TysonMN opened 1 year ago

TysonMN commented 1 year ago

Requested by @ploeh in this tweet.

ploeh commented 1 year ago

Thank you for creating this issue. In case Twitter stops working, here's the full contents of the tweet:

@tyson_mn @nikosbaxevanis How do I write an asynchronous #fsharp Hedgehog property (preferably with System.Threading.Tasks)?

(Links automatically created by the copy-paste action from Twitter.)

FWIW, Task-based properties are possible in FsCheck 3 (which is in perpetual beta) - just in case someone wants some inspiration.

TysonMN commented 1 year ago

Why do you want a Task-based API (instead of wanting an Async-based API and converting your Task to Async, which would be idiomatic F#)?

ploeh commented 1 year ago

Everything that's asynchronous in .NET is Task-based, and it's in the context of testing things like that that I need the feature. F# now has a task computation expression in addition to async, so regardless on what one might feel about the advantages and drawbacks of the two options, I think it's safe to assume that at this point, F# Async is legacy. It's Betamax versus VHS all over again 🤷

cmeeren commented 1 year ago

I think it's safe to assume that at this point, F# Async is legacy. It's Betamax versus VHS all over again 🤷

What makes you say that? Async still has benefits (implicit cancellation token passing and tail recursion), and Async programming in F# says explicity to prefer Async:

In general, you should use async { } programming in F# unless you frequently need to create or consume .NET tasks.

ploeh commented 1 year ago

@cmeeren, fair enough, Async does have its uses. In fact, I can even think of a few myself, where Async would still be better than Task, but they're uncommon. I could have been more nuanced in my wording.

To be clear, I wouldn't mind if Hedgehog supports testing against Async in addition to Task, but I don't expect to use it much.

When I need to test something asynchronous, it's always because I'm working with some kind of .NET API, and they're always Task-based. Here's a typical example: Self-hosted integration tests in ASP.NET. These tests are almost always integration tests (or whatever you want to call them) that test how multiple components integrate with framework code. When working on that level, I can't choose between Async and Task; the latter is a given.

As soon as I change detail level to write smaller tests (unit tests), I know how to design code so that it's not asynchronous at all. Thus, I don't need this feature for unit testing; I need it for integration testing, and therefore I need it for Task.

cmeeren commented 1 year ago

Thanks for the clarification. I find it interesting that you use Hedgehog (property-driven, lots of test repetitions) for integration tests. I'd be interested in reading an article on that at some point!

ploeh commented 1 year ago

I find it interesting that you use Hedgehog (property-driven, lots of test repetitions) for integration tests.

I don't, because I can't (because I can't write asynch properties). I use FsCheck instead.

I'd be interested in reading an article on that at some point!

Keep an eye on this article series 😉

dharmaturtle commented 1 year ago

A relevant excerpt from the fsharp-hedgehog-xunit docs:

If the test returns Async<_> or Task<_>, then Async.RunSynchronously is called, which blocks the thread. This may have significant performance implications as tests run 100 times by default.

[<Property>]
let ``Async with exception shrinks`` (i: int) = async {
  do! Async.Sleep 100
  if i > 10 then
    failwith "whoops!"
  }

=== Output ===
Hedgehog.FailedException: *** Failed! Falsifiable (after 12 tests):
(11)

Relevant source code.

Grepping FsCheck for Async.AwaitTask and Async.RunSynchronously yields nothing interesting, which makes me think they're handling it more intelligently than I am.