等待不在 SpecFlow 异步测试步骤中返回
Await not returning in SpecFlow async test step
我在处理一些异步 SpecFlow 单元测试时遇到问题,其中 await 永远不会 returning。我认为它与 SpecFlow SynchronizationContext 有关,但我不太确定它是如何工作的。
class 我正在编写测试以在 IScheduler 上定期运行代码。此代码等待在外部事件上完成的任务。在单元测试中,IScheduler 是一个 TestScheduler,外部事件任务由一些存根代码模拟。
我能够让它工作的唯一方法是确保从 同步 SpecFlow 测试步骤调用等待和任务完成。
- 如果从 异步 测试步骤调用等待,则等待永远不会 returns.
- 如果 await 是从 synchronous 测试步骤调用的,而任务完成是从 asynchronous 测试步骤调用的,那么 await 将return,但在工作线程上,因此不受单元测试的控制,所以我必须延迟以确保它发生。
- 如果从 同步 测试步骤调用等待和任务完成,则等待将 return 在与单元测试相同的线程上同步。
我的假设是,当从 异步 测试步骤调用 await 时,会捕获 SynchronizationContext.Current
并且当任务完成时它会尝试 return在该 SynchronizationContext 上,但它不再有效,因为它是先前的测试步骤。当从 synchronous 测试步骤调用时,我注意到 SynchronizationContext.Current
为空,我认为这就是等待 returns 与任务完成同步的原因(这就是我要测试)
我可以对 await and/or 任务完成做些什么吗(两者都在测试存根代码中)以确保 await 忽略 SynchronizationContext.Current
并始终同步 returns测试?
我正在使用 SpecFlow 3.9.58
我能够使用以下代码重现此问题:
Feature: Await
Scenario: Wait Async, Set Async
Given we await asynchronously
When we set asynchronously
Then await should have completed
Scenario: Wait Sync, Set Sync
Given we await synchronously
When we set synchronously
Then await should have completed
Scenario: Wait Async, Set Sync
Given we await asynchronously
When we set synchronously
Then await should have completed
Scenario: Wait Sync, Set Async
Given we await synchronously
When we set asynchronously
Then await should have completed
Scenario: Wait Sync, Set Async, Delay
Given we await synchronously
When we set asynchronously
And we wait a bit
Then await should have completed
using Microsoft.Reactive.Testing;
using NUnit.Framework;
using System.Reactive.Concurrency;
using System.Threading;
using System.Threading.Tasks;
using TechTalk.SpecFlow;
namespace Test.SystemTests
{
[Binding]
public sealed class AwaitSteps
{
private TestScheduler _scheduler = new TestScheduler();
private TaskCompletionSource _tcs;
private bool _waiting;
// The previous Wait() implementation before Zer0's answer
//private async Task Wait()
//{
// _tcs = new TaskCompletionSource();
// _waiting = true;
// await _tcs.Task;
// _waiting = false;
//}
// Updated Wait()/NestedWait() following Zer0's answer
private async Task NestedWait()
{
// Simulate the code in the stub class (can change this)
_tcs = new TaskCompletionSource();
await _tcs.Task.ConfigureAwait(false);
}
private async Task Wait()
{
// Simulate the code in the class under test (prefer not to change this)
_waiting = true;
await NestedWait();
_waiting = false;
}
private void WaitOnScheduler()
{
_scheduler.Schedule(async () => await Wait());
_scheduler.AdvanceBy(1);
}
private void Set() => _tcs.TrySetResult();
[Given(@"we await asynchronously")]
public async Task GivenWeAwaitAsynchronously()
{
await Task.CompletedTask; // Just to make the step async
WaitOnScheduler();
}
[Given(@"we await synchronously")]
public void GivenWeAwaitSynchronously() => WaitOnScheduler();
[When(@"we set asynchronously")]
public async Task WhenWeSetAsynchronously()
{
await Task.CompletedTask; // Just to make the step async
Set();
}
[When(@"we set synchronously")]
public void WhenWeSetSynchronously() => Set();
[When(@"we wait a bit")]
public async Task WhenWeWaitABit() => await Task.Delay(100);
[Then(@"await should have completed")]
public void ThenAwaitShouldHaveCompleted() => Assert.AreEqual(false, _waiting);
}
}
My assumption is that when the await is called from an asynchronous test step then SynchronizationContext.Current is captured and when the task completes it tries to return on that SynchronizationContext
可以使用 ConfigureAwait(false)
进行更改。我一个人做了那个改变:
private async Task Wait()
{
_tcs = new TaskCompletionSource();
_waiting = true;
await _tcs.Task.ConfigureAwait(false);
_waiting = false;
}
并得到了这些结果,使用你的代码逐字减去那个变化。
关于必须一直调用 ConfigureAwait(false)
的问题,有几种解决方法。 useful documentation 所有这些,包括详细信息和避免捕获的方法,例如简单的 Task.Run
调用。
在这种情况下,最简单的方法是在设置场景时执行此操作:
SynchronizationContext.SetSynchronizationContext(null);
这完全消除了 ConfigureAwait
的需要,因为没有什么可捕获的。
还有其他选择。如果这不能回答您的问题,请参阅链接的文章或评论。
据我所知,没有公开的 属性 任何地方可以更改默认行为,因此无论如何都需要一些代码。
就是说 ConfigureAwait(false)
当其他编码人员阅读它时,您可以非常明确地说明您在做什么。所以如果你想要一个不同的解决方案,我会记住这一点。
我在处理一些异步 SpecFlow 单元测试时遇到问题,其中 await 永远不会 returning。我认为它与 SpecFlow SynchronizationContext 有关,但我不太确定它是如何工作的。
class 我正在编写测试以在 IScheduler 上定期运行代码。此代码等待在外部事件上完成的任务。在单元测试中,IScheduler 是一个 TestScheduler,外部事件任务由一些存根代码模拟。
我能够让它工作的唯一方法是确保从 同步 SpecFlow 测试步骤调用等待和任务完成。
- 如果从 异步 测试步骤调用等待,则等待永远不会 returns.
- 如果 await 是从 synchronous 测试步骤调用的,而任务完成是从 asynchronous 测试步骤调用的,那么 await 将return,但在工作线程上,因此不受单元测试的控制,所以我必须延迟以确保它发生。
- 如果从 同步 测试步骤调用等待和任务完成,则等待将 return 在与单元测试相同的线程上同步。
我的假设是,当从 异步 测试步骤调用 await 时,会捕获 SynchronizationContext.Current
并且当任务完成时它会尝试 return在该 SynchronizationContext 上,但它不再有效,因为它是先前的测试步骤。当从 synchronous 测试步骤调用时,我注意到 SynchronizationContext.Current
为空,我认为这就是等待 returns 与任务完成同步的原因(这就是我要测试)
我可以对 await and/or 任务完成做些什么吗(两者都在测试存根代码中)以确保 await 忽略 SynchronizationContext.Current
并始终同步 returns测试?
我正在使用 SpecFlow 3.9.58
我能够使用以下代码重现此问题:
Feature: Await
Scenario: Wait Async, Set Async
Given we await asynchronously
When we set asynchronously
Then await should have completed
Scenario: Wait Sync, Set Sync
Given we await synchronously
When we set synchronously
Then await should have completed
Scenario: Wait Async, Set Sync
Given we await asynchronously
When we set synchronously
Then await should have completed
Scenario: Wait Sync, Set Async
Given we await synchronously
When we set asynchronously
Then await should have completed
Scenario: Wait Sync, Set Async, Delay
Given we await synchronously
When we set asynchronously
And we wait a bit
Then await should have completed
using Microsoft.Reactive.Testing;
using NUnit.Framework;
using System.Reactive.Concurrency;
using System.Threading;
using System.Threading.Tasks;
using TechTalk.SpecFlow;
namespace Test.SystemTests
{
[Binding]
public sealed class AwaitSteps
{
private TestScheduler _scheduler = new TestScheduler();
private TaskCompletionSource _tcs;
private bool _waiting;
// The previous Wait() implementation before Zer0's answer
//private async Task Wait()
//{
// _tcs = new TaskCompletionSource();
// _waiting = true;
// await _tcs.Task;
// _waiting = false;
//}
// Updated Wait()/NestedWait() following Zer0's answer
private async Task NestedWait()
{
// Simulate the code in the stub class (can change this)
_tcs = new TaskCompletionSource();
await _tcs.Task.ConfigureAwait(false);
}
private async Task Wait()
{
// Simulate the code in the class under test (prefer not to change this)
_waiting = true;
await NestedWait();
_waiting = false;
}
private void WaitOnScheduler()
{
_scheduler.Schedule(async () => await Wait());
_scheduler.AdvanceBy(1);
}
private void Set() => _tcs.TrySetResult();
[Given(@"we await asynchronously")]
public async Task GivenWeAwaitAsynchronously()
{
await Task.CompletedTask; // Just to make the step async
WaitOnScheduler();
}
[Given(@"we await synchronously")]
public void GivenWeAwaitSynchronously() => WaitOnScheduler();
[When(@"we set asynchronously")]
public async Task WhenWeSetAsynchronously()
{
await Task.CompletedTask; // Just to make the step async
Set();
}
[When(@"we set synchronously")]
public void WhenWeSetSynchronously() => Set();
[When(@"we wait a bit")]
public async Task WhenWeWaitABit() => await Task.Delay(100);
[Then(@"await should have completed")]
public void ThenAwaitShouldHaveCompleted() => Assert.AreEqual(false, _waiting);
}
}
My assumption is that when the await is called from an asynchronous test step then SynchronizationContext.Current is captured and when the task completes it tries to return on that SynchronizationContext
可以使用 ConfigureAwait(false)
进行更改。我一个人做了那个改变:
private async Task Wait()
{
_tcs = new TaskCompletionSource();
_waiting = true;
await _tcs.Task.ConfigureAwait(false);
_waiting = false;
}
并得到了这些结果,使用你的代码逐字减去那个变化。
关于必须一直调用 ConfigureAwait(false)
的问题,有几种解决方法。 useful documentation 所有这些,包括详细信息和避免捕获的方法,例如简单的 Task.Run
调用。
在这种情况下,最简单的方法是在设置场景时执行此操作:
SynchronizationContext.SetSynchronizationContext(null);
这完全消除了 ConfigureAwait
的需要,因为没有什么可捕获的。
还有其他选择。如果这不能回答您的问题,请参阅链接的文章或评论。
据我所知,没有公开的 属性 任何地方可以更改默认行为,因此无论如何都需要一些代码。
就是说 ConfigureAwait(false)
当其他编码人员阅读它时,您可以非常明确地说明您在做什么。所以如果你想要一个不同的解决方案,我会记住这一点。