【问题标题】:Share Data Between Threads when using SpecFlow + SpecRunner使用 SpecFlow + SpecRunner 时在线程之间共享数据
【发布时间】:2022-04-29 03:11:22
【问题描述】:

我正在开发一个使用 SpecFlow + SpecRunner 和 XUnit 的测试套件实现。我们正在尝试进行并行测试执行,我想知道是否有一种方法可以在测试运行开始时运行挂钩并将令牌值存储在静态变量中,以便可以在线程之间共享.

总结一下,specflow 提供了一种在并行执行期间在线程之间共享数据的机制。

【问题讨论】:

    标签: c# specflow


    【解决方案1】:

    我们可以使用以下任何一种方法共享数据

    1. 场景上下文
    2. 上下文注入

    在这里,方法 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:上下文注入细节可以参考下面的链接,并附上一个例子

    Context Injection

    【讨论】:

    • 这不仅不会在线程之间共享状态,当与并行执行一起使用时,它实际上会throw an exception。请参阅有关访问静态上下文的说明。
    • @Zero:你试过这个吗?当我用小例子尝试方法 1 时,它对我有用。它没有抛出任何异常,我能够从场景上下文中读取值。
    • 是的。您是否使用并行执行?并非所有的 specflow 测试运行器都支持它。
    • 是的。并行执行。我已经使用 'testThreadCount="5"' 进行了测试,并且我正在使用 Specflow Specrun 来执行。我已经更新了答案中的其他支持点。请检查
    • 这不适用于并行执行
    【解决方案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);
        }
    }
    

    【讨论】:

    • 让我试试这个方法
    • @HusniJabir 有什么问题吗?如果是这样,请编辑问题,以便我解决。
    • 这对我不起作用 - 每个线程似乎都有自己的 ScenarioContext 实例。
    • 问题是在并行运行时如何在线程间共享变量。
    • @FrankEscobar 已更新。如果仍然不够,请告诉我原因,我会尽力提供进一步的帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-01
    • 1970-01-01
    相关资源
    最近更新 更多