对ScenarioContext.Current的一个有用的抽象

本文关键字:一个 有用 抽象 ScenarioContext Current | 更新日期: 2023-09-27 18:13:00

这个问题更多的是关于SpecFlow中共享值的一般性讨论。请根据您在SpecFlow中的经验提供任何建设性的反馈。

我对这项技术比较陌生,当寻找在步骤定义文件之间共享值的解决方案时,我发现了ScenarioContext.Current.GetScenarioContext.Current.Set。这些都很方便,但在我看来,有几个问题。

  1. 在这种方法中有相当多的输入。
  2. 值类型通过字符串索引插入和检索,因此需要使用字符串常量或枚举来确保步骤定义之间的一致性。
  3. 假设您试图检索的值已插入可能不安全。

我想出了一个抽象的概念,我认为它使一些事情更容易接受,我想知道人们是怎么想的。

问题:我的值设置了吗?

我的解决方案是将ScenarioContext.Current包装在一个单例访问器类中。这个类的行为类似于ScenarioContext.Current,除了它在找不到值时抛出AssertInconclusiveException

    private static ScenarioContextAccessor instance;
    public static ScenarioContextAccessor Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new ScenarioContextAccessor();
            }
            return instance;
        }
    }
    private ScenarioContextAccessor() { }
    public T Retrieve<T>(string index)
    {
        try
        {
            T val = (T)ScenarioContext.Current[index];
            if (val == null)
            {
                throw new Exception();
            }
            return val;
        }
        catch
        {
            throw new AssertInconclusiveException(index + " of type " + typeof(T).Name + " was not found in the current scenario context. Did you execute your steps out of order?");
        }
    }
    public T Retrieve<T>()
    {
        try
        {
            T val = ScenarioContext.Current.Get<T>();
            if (val == null)
            {
                throw new Exception();
            }
            return val;
        }
        catch
        {
            throw new AssertInconclusiveException("No object of type " + typeof(T).Name+ " could be found in the current scenario context. Did you execute your steps out of order?");
        }
    }
    public void Set(string index, object value)
    {
        ScenarioContext.Current[index.ToLower(CultureInfo.InvariantCulture)] = value;
    }
    public void Set<T>(T value)
    {
        ScenarioContext.Current.Set<T>(value);
    }
}

问题:这需要太多的输入!

我的解决方案是有任何需要这些值的步骤定义,将它们定义为由ScenarioContextAccessor备份的私有属性。任何访问值类型的属性都使用字符串常量作为索引。

    private string FolderName
    {
        get
        {
            return ScenarioContextAccessor.Instance.Retrieve<string>(FolderingScenarioContextKey.FolderName);
        }
        set
        {
            ScenarioContextAccessor.Instance.Set(FolderingScenarioContextKey.FolderName, value);
        }
    }
    private UserDocumentMetadata Metadata
    {
        get
        {
            return ScenarioContextAccessor.Instance.Retrieve<UserDocumentMetadata>();
        }
        set
        {
            ScenarioContextAccessor.Instance.Set<UserDocumentMetadata>(value);
        }
    }

所以现在我可以很容易地访问我的共享值,就好像它们是简单的属性一样。

请提供任何建设性的反馈。谢谢!

对ScenarioContext.Current的一个有用的抽象

关于第一部分,我不同意。我发现如果数据没有正确设置,测试失败更有用。

对于第2部分,我已经使用了这样的模式,特别是用于获取被测试对象的实例,因为它节省了大量的输入&潜在错误。

如果您只需要一个类的虚拟实例,另一个有用的模式(取决于具体情况)是延迟初始化方法,而不是第一部分解决方案:

public static Mock<T> GetOrMockAndStore<T>() where T : class
    {
        Mock<T> output;
        if (ScenarioContext.Current.TryGetValue(out output))
        {
            return output;
        }
        else
        {
            output = new Mock<T>();
            ScenarioContext.Current.Set(output);
        }
        return card;
    }

我正在使用Moq -非常有用的框架。

我认为你正在寻找的功能是上下文注入(依赖注入):https://github.com/techtalk/SpecFlow/wiki/Context-Injection

这允许您在步骤定义之间共享类