【发布时间】:2022-04-29 03:11:22
【问题描述】:
我正在开发一个使用 SpecFlow + SpecRunner 和 XUnit 的测试套件实现。我们正在尝试进行并行测试执行,我想知道是否有一种方法可以在测试运行开始时运行挂钩并将令牌值存储在静态变量中,以便可以在线程之间共享.
总结一下,specflow 提供了一种在并行执行期间在线程之间共享数据的机制。
【问题讨论】:
我正在开发一个使用 SpecFlow + SpecRunner 和 XUnit 的测试套件实现。我们正在尝试进行并行测试执行,我想知道是否有一种方法可以在测试运行开始时运行挂钩并将令牌值存储在静态变量中,以便可以在线程之间共享.
总结一下,specflow 提供了一种在并行执行期间在线程之间共享数据的机制。
【问题讨论】:
我们可以使用以下任何一种方法共享数据
在这里,方法 1 和 2 在多线程中不会有任何问题。因为,上下文注入的生命周期是特定于场景级别的。
方法一:我们可以在BeforeScenario钩子中定义Token Generation Step,生成的Token值可以在ScenarioContext中更新。
我们可以在下面的任何地方直接从场景上下文中访问令牌
这里,每个场景运行前都会生成Token,不会影响并行执行。更多详情,Parallel-Execution
Scenarios及其相关的hook(Before/Afterscenario、scenario block、step)在执行过程中被隔离在不同的线程中,互不阻塞。每个线程都有一个单独的(和隔离的)ScenarioContext。
钩子类:
public class CommonHooks
{
[BeforeScenario]
public static void Setup()
{
// Add Token Generation Step
var adminToken = "<Generated Token>";
ScenarioContext.Current["Token"] = adminToken;
}
}
步骤类:
[Given(@"I Get the customer details""(.*)""")]
public void WhenIGetTheCustomerDetails(string endpoint)
{
if(ScenarioContext.Current.ContainsKey("Token"))
{
var token = ScenarioContext.Current["Token"].ToString();
//Now the Token variable holds the token value from the scenario context and It can be used in the subsequent steps
}
else
{
Assert.Fail("Unable to get the Token from the Scenario Context");
}
}
如果您希望在多个 Step 之间共享同一个 token,那么您可以在构造函数中分配这个 token 值并且可以使用它
例如,
[Binding]
public class CustomerManagementSteps
{
public readonly string token;
public CustomerManagementSteps()
{
token= ScenarioContext.Current["Token"].ToString();
}
[Given(@"I Get the customer details""(.*)""")]
public void WhenIGetTheCustomerDetails(string endpoint)
{
//Now the Token variable holds the token value from the scenario context and It can be used in the subsequent steps
}
}
方法2:上下文注入细节可以参考下面的链接,并附上一个例子
【讨论】:
更新
鉴于不赞成票和 cmets,我更新了我的代码示例,以更好地展示您可以在此处将依赖注入与您自己设计的代码一起使用的一种方式。此共享数据将持续场景的生命周期,并被所有绑定使用。我想这就是你要找的东西,除非我弄错了。
//Stores whatever data you want to share
//Write this however you want, it's your code
//You can use more than one of these custom data classes of course
public class SomeCustomDataStructure
{
//If this is run in paralell, this should be thread-safe. Using List<T> for simplicity purposes
//Use EF, ConcurrentCollections, synchronization (like lock), etc...
//Again, do NOT copy this code for parallel uses as List<int> is NOT thread-safe
//You can force things to not run in parallel so this can be useful by itself
public List<int> SomeData { get; } = new List<int>();
}
//Will be injected and the shared instance between any number of bindings.
//Lifespan is that of a scenario.
public class CatalogContext : IDisposable
{
public SomeCustomDataStructure CustomData { get; private set; }
public CatalogContext()
{
//Init shared data however you want here
CustomData = new SomeCustomDataStructure();
}
//Added to show Dispose WILL be called at the end of a scenario
//Feel free to do cleanup here if necessary.
//You do NOT have to implement IDiposable, but it's supported and called.
public void Dispose()
{
//Below obviously not thread-safe as mentioned earlier.
//Simple example is all.
CustomData.SomeData.Clear();
}
}
[Binding]
public class SomeSteps
{
//Data shared here via instane variable, accessable to multiple steps
private readonly CatalogContext catalogContext;
//Dependency injection handled automatically here.
//Will get the same instance between other bindings.
public SomeSteps(CatalogContext catalogContext)
{
this.catalogContext = catalogContext;
}
[Given(@"the following ints")]
public void GivenTheFollowingInts(int[] numbers)
{
//This will be visible to all other steps in this binding,
//and all other bindings sharing the context
catalogContext.CustomData.SomeData.AddRange(numbers);
}
}
【讨论】: