-
Notifications
You must be signed in to change notification settings - Fork 23
Support for async steps #23
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
Support for async steps #23
Conversation
|
🤔 does this have any purpose if it's not being surfaced all the way up to the xunit async/task ? I'd tend to do a spike making the main runner API async all the way to the top and seeing how messy that gets before introducing the complexity of sync over async. (my thought is tempered by it not touching many things, having examples and being small, I must admit) |
|
@bartelink The purpose of this is to allow testing asynchronous code without need make steps synchronous. Basically the author of BDD can write asynchronous call all way up in his library without Task.Wait or Async.RunSynchronously which makes the code nice and good for reusing e.g. in F# scripts (and without deadlocks). |
|
OK, I accept the purpose - but does it have any point ;) |
|
@bartelink The main point for nearly all unit testing frameworks to introduce async test is really just to support testing using async APIs. See the introductory posts for individual framework:
You can see that none of the blogs write that they introduced it to improve efficient parallelism. The NUnit blog even writes that they did basically the same what I did: FsCheck.xUnit contains async support in version 3. SpecFlow supports async steps, too. I would like to write full async support but it really doesn't seem to me trivial today. This change fulfills the value that I am looking for. |
|
OK, thanks for taking the time to lay that out. I'm not entirely against against merging but the points you make for me all have counterpoints:
So, my one and only point remains this - putting support in this is not for free if nobody then completes the deal and adds proper parallelism; it adds complexity to the codebase without really unblocking anything (i.e. people can already do their own wrapping). I'm not making this point with the goal of stopping you, just to raise that there are two ways of looking at how to address this :
If this is unblocking people, an MVP stance makes sense. If ultimately the intention is to do it all, I'm just saying "Hm, maybe it makes sense to do the hard bit first". |
|
I somehow agree with both of you. Have you made a research what would be needed for the final correct solution? What it would take to implement the async properly? Probably the code should generate some It seems to me, that maybe the emit was taken too far. Especially for the asynchronous scenario it would make perfect sense to limit the emit code just for the usable purpose - it can generate just a small wrapper for steps, so it will be possible to debug the tests. My question is, how hard it is to implement it properly. If it is hard can we at least update this implementation in a way that it will be somehow helpful to the final one? |
|
Re the updating FSharp.Core - is there any way to avoid that ? In general it's kinder to dual target and shim for F# pre 4.5 (or (I'm not paying complete attention) does the readme already document a min version strategy - there's definitely a case to be made for not supporting old stuff where that helps with implementation simplicity and/ot testing) |
|
I have done some research and the proper implementation seams achievable but I don't think I will find time to make it fully any time soon. If you have time to do it within next weeks than we can skip this pull request. However, I think @bartelink opened good question regarding how the final implementation should look like and whether this is step forward to it. The one thing is if the code should have both asynchronous and synchronous implementation. The home page states "we want to keep TickSpec powerful, but minimal". I think if we had both variants in code properly then it would increase amount of code by large margin without adding a big value. So, my preference is to have asynchronous variant only in the future to keep TickSpec easier to maintain. What do you think about that? If you agreed with my view then I think the steps from user perspective will look like in this pull request. I could also make the method scenario.Action.Invoke() asynchronous to make even the wiring look like in future but that would be kind of fake. Or I could even do the non-ILed version final. The other perspective regarding proper implementation is if we want use Tasks or Async. I think we should allow both on steps (like I did in the pull request) but the method scenario.Action.Invoke() should be at the end at minimum Task based so that it can be used from C# easily, so scenario.Action.InvokeAsync(), but we could have both variants. Within the TickSpec implementation I would use what is simpler and easier to maintain. It looks to me today that Async will be simpler to generate/maintain in the genIL code even though I could make even the task based probably relatively condensed when using Task.ContinueWith. So, regardless I still feel the change in the pull request is step forward the right direction. Regarding FSharp.Core version, I today actually don't see a reason why not to take the newest FSharp.Core which is now 4.5.2. I though that the reason we used 3.1.2.5 was to ensure that somebody can still develop TickSpec in Visual Studio 2013 so that we don't introduce F# 4 features into TickSpec code. However, the changes for .NET Core made a requirement that you need to have now a recent version of Visual Studio 2017 so you basically have even F# 4.5 in place. So, my opinion is to let's use the oportunity and when writing new code let's base on F# 4.5 - probably we will stick on that version then for some time. Btw. I was missing non-generic AwaitTask in 3.1.2.5 but I can't put work-around if necessary. |
|
Here's an In general I'd favor sticking with Async for simplicity of maintenance, even if some Task tricks can wring out perf from time to time. In general, as Gherkin based tests tend to be on the acceptance level of the test triangle, I'd submit that it's pretty clear that one can afford to go Async in the engine. Where there's a will and a need, there's normally a way to support For me, doing multitargeting as in CallPolly seems pretty doable; There are plenty codebases out there that have tests targeting xUnit 1.9 etc, and unless it's really painful, keeping that backcompat is pretty important (and IME doing these types of things 'the right way' in a library can be a valuable exercise in terms of how to manage these same transitions at app level). The powerful but minimal maxim is a random thought I typed out, but in general the idea of that should be that the code should remain roughly in line with how Phil implemented it - compact, not a monolith, but also not tonnes of files - this leaves it in the best place for people to grab it and do things with it; ultimately if someone can use it to learn F# techniques, fork it do something only tangentially related to running under a .NET test runner, or port to a more basic platform, that's also valuable. |
Well, it may not make a reason for new implementation but some implementations may use older version. It should be up to them. We should require the minimum version we really need.
That would be probably the biggest breaking change introduced. At the same moment, it will be somehow weird to use it from a testing framework that does not support async. At the same moment, I would be probably fine with having the synchronous method implemented using the active waiting on the asynchronous one.
Agree. If you are able to prepare an implementation without the emited code. What if we emit just a dynamic method that will (as a parameter) get a method to call and its parameters? If we reduce the emit code just to that, it will be simplified while keeping the same functionality from user perspective. Integrating such an emit to the implementation should be simple. I will try to find some time to play with the emits to see whether I will be able to reduce it just to such wrappers. |
|
Ok, I am fine waiting for somebody implementing full async support, hopefully it won't take long time |
This change adds support for steps that return:
TaskTask<Unit>Task<SomeReturnValue>Async<Unit>Async<SomeReturnValue>the implementation is currently simple, it wraps it into
Async.RunSynchronously. We could later support async all way into test frameworks but that is out of scope of this change.