SpecFlow 功能可以在步骤之间共享吗?

发布于 2025-01-08 06:33:52 字数 220 浏览 0 评论 0原文

我们希望从三个不同的角度测试代码:

  • 内部(直接调用)
  • Web 服务
  • Web 应用程序

由于我们不想编写三次功能文件,因此似乎我们应该在步骤之间共享功能,但我可以除了可能在 VS 项目之间共享文件之外,我不知道如何做到这一点,这似乎总是有点不稳定。

在项目之间共享功能文件是实现此目的的最佳方法,还是有更明智的方法?

We have code which we'd like to test from three different angles:

  • Internally (direct call)
  • Web service
  • Web application

As we don't want to write feature files three times, it seems that we should share features between Steps, but I can't figure out how this might be done, aside from perhaps sharing files between VS projects, which has always seemed a bit, well, flaky.

Is sharing feature files between projects the best way to achieve this, or is there a more sensible method?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

演出会有结束 2025-01-15 06:33:52

我知道这个问题很久以前就被问过,但我认为你想要的是可能的,而且据我了解,有几种方法可以实现它。实际上,您真正想要的是定义一次功能,但根据您是调用内部服务、公共 Web 服务还是 Web 应用程序来切换为该功能调用的步骤。 specflow 邮件列表中讨论了解决此问题的各种方法< /a> 但其要点如下(示例显示了 API 与 UI 方法,但我相信这同样适用于您的内部与 Web 服务与 webapp):

选项 1 #

(感谢 Oliver Friedrich(来自邮件列表),

您需要为要针对步骤进行更改的每个选项创建一个程序集,因此用于内部,一种用于 Web 服务,一种用于 Web 应用程序。您告诉specflow有关配置中所有不同程序集的信息,如下所示:

<specFlow>
  <stepAssemblies>
    <stepAssembly assembly="Tests.API" />
    <stepAssembly assembly="Tests.UI" />
  </stepAssemblies>
</specFlow>

然后在您的常见测试步骤中,您有一些 [BeforeTestRun] 步骤,用于选择从中加载步骤的程序集:

[Binding]
public class TestRunSetup {

    // this method needs to be static
    [BeforeTestRun]
    public static void BeforeTestRun() 
    {
        if (RunApiTests()) // <-- implement this method to choose whether to run the tests via your chosen method 
        {
            Assembly.Load("Tests.API");
        }
        else 
        {
            Assembly.Load("Tests.UI");
        }
    }
}

选项 2 ##

(感谢 Gáspár Nagy(来自邮件列表)

尝试 在构建期间生成测试。我不确定这到底是如何运作的,但这是我们可以调查的一个领域。

选项 3 ##

(感谢邮件列表中的 Dan Mork

如果您使用 SpecFlow 的依赖注入功能,那么您可以创建一个接口来执行您想要执行的调用,并在一组通用步骤中使用它。然后,您可以拥有该接口的 3 种实现,一种调用您的内部服务,一种调用 Web 服务,一种操作 Web 应用程序。剩下的就是将该接口的正确实现注入到specflow步骤文件中,可以像这样完成:

// the abstract concept of the system that could be implemented with 
Selenium, HttpClient, etc. 
public interface IDocument 
{ 
    string Title { get;} 
    void Load(); 
} 

// the steps that are executed when the scenarios from your feature 
file are executed 
[Binding] 
public class Steps 
{ 
    private readonly IDocument _document; 

    public Steps(IDocument document) 
    { 
        _document = document; 
    } 

    [Given("something")] 
    public void GivenSomething() 
    { 
        // set up given state 
    } 

    [When("I view the document")] 
    public void WhenIViewTheDocument() 
    { 
        _document.Load(); 
    } 

    [Then(@"the title should be ""(.*)""")] 
    public void Then(string title) 
    { 
        Assert.ArEqual(_document.Title, title); 
    } 
} 

// this is where the magic happens - get the dependency injection 
// container and register an IDocument implementation
[Binding] 
public class Dependencies 
{ 
    private readonly IObjectContainer _objectContainer; 

    public Dependencies(IObjectContainer objectContainer) 
    { 
        _objectContainer = objectContainer; 
    } 

    [BeforeScenario] 
    public void RegisterDocumentInterfaces() 
    { 
        // register the correct IDocument implementation - UI or API 
    } 
} 

这仍然给您带来了如何知道哪个接口的问题
实施注册。这将取决于您的具体情况
解决方案、您的构建环境、您的测试执行环境、
等等。一些选项...

  • BeforeScenarioAttribute 的标记参数
  • 为您的项目创建两个不同的构建配置
    每个定义不同的常量并使用预编译器指令
    在代码中构建正确的注册
  • 添加条件逻辑以检查环境变量
  • 添加条件逻辑以检查配置设置
  • 我尚未检查,但也许 ScopeAttribute 可扩展为
    您创建一个子类并提供您自己的逻辑是否
    执行 BeforeScenarioAttribute 方法。

希望这会给你一些选择来做你想做的事。

恕我直言,第一个选项可能是最好的,尽管选项 3 也很不错。

I know this was asked quite a while ago, but what I think you want is possible, and there are several ways to achieve it as I understand. In effect what you really want is to define the feature once but switch out the steps that are called for the feature based on whether you are calling the Internal service, the public webservice or the WebApp. There is a discussion of the various approaches to solve this on the mailing list for specflow but the gist of it is as follows (example show an API vs UI approach but the same applies to your Internal vs web service vs webapp I believe):

Option 1 #

(thanks to Oliver Friedrich from the mailing list)

you need to create one assembly for each of the options you want to vary for the steps, so one for internal, one for web service and one for webapp. You tell specflow about all of the different assemblies in the config, like so:

<specFlow>
  <stepAssemblies>
    <stepAssembly assembly="Tests.API" />
    <stepAssembly assembly="Tests.UI" />
  </stepAssemblies>
</specFlow>

then in your common test steps you have some [BeforeTestRun] step which chooses which assembly to load the steps from:

[Binding]
public class TestRunSetup {

    // this method needs to be static
    [BeforeTestRun]
    public static void BeforeTestRun() 
    {
        if (RunApiTests()) // <-- implement this method to choose whether to run the tests via your chosen method 
        {
            Assembly.Load("Tests.API");
        }
        else 
        {
            Assembly.Load("Tests.UI");
        }
    }
}

Option 2 ##

(thanks to Gáspár Nagy from the mailing list)

Try generating the tests during the build. I'm not sure exactly how this will work but it is an area we can investigate.

Option 3 ##

(thanks to Dan Mork from the mailing list)

If you use SpecFlow's dependency injection capabilities then you can create an interface for executing the calls you want to do and use this in a common set of steps. Then you can have 3 implementations of that interface, one which calls your internal service, one which calls the web service and one which manipulates the web app. All that then remains is to inject the correct implementation of that interface into the specflow step files, which can be done like this:

// the abstract concept of the system that could be implemented with 
Selenium, HttpClient, etc. 
public interface IDocument 
{ 
    string Title { get;} 
    void Load(); 
} 

// the steps that are executed when the scenarios from your feature 
file are executed 
[Binding] 
public class Steps 
{ 
    private readonly IDocument _document; 

    public Steps(IDocument document) 
    { 
        _document = document; 
    } 

    [Given("something")] 
    public void GivenSomething() 
    { 
        // set up given state 
    } 

    [When("I view the document")] 
    public void WhenIViewTheDocument() 
    { 
        _document.Load(); 
    } 

    [Then(@"the title should be ""(.*)""")] 
    public void Then(string title) 
    { 
        Assert.ArEqual(_document.Title, title); 
    } 
} 

// this is where the magic happens - get the dependency injection 
// container and register an IDocument implementation
[Binding] 
public class Dependencies 
{ 
    private readonly IObjectContainer _objectContainer; 

    public Dependencies(IObjectContainer objectContainer) 
    { 
        _objectContainer = objectContainer; 
    } 

    [BeforeScenario] 
    public void RegisterDocumentInterfaces() 
    { 
        // register the correct IDocument implementation - UI or API 
    } 
} 

That still leaves you with the problem of how to know which
implementation to register. That will depend on the specifics of your
solution, your build environment, your test execution environment,
etc. Some options for that...

  • tag parameter to the BeforeScenarioAttribute
  • create two different build configurations for your project that
    each define different constants and use precompiler directives to
    build the right registrations into the code
  • add conditional logic to check an environment variable
  • add conditional logic to check a configuration setting
  • I haven't checked, but perhaps ScopeAttribute is extensible for
    you to create a subclass and provide your own logic for whether or not
    a BeforeScenarioAttribute method is executed.

Hopefully this will give you some options to do what you want.

IMHO the first option is probably the best, though option 3 is pretty sweet as well.

酒中人 2025-01-15 06:33:52

我们有一个类似的情况,相同的功能必须适用于不同的输入法。
我们的解决方案与 Sam Holder 的答案中的选项 #3 类似,但我们希望使用配置文件而不是代码来决定在每种情况下使用哪种实现。

这可以通过我们自己的 DI 机制来实现。除了能够在运行时注册实现之外,它还在第一次调用时读取配置文件并加载所描述的类。

现在,对于每个与另一个项目共享功能的项目,我们都有一个 .config 文件,其中我们声明每个共享接口(主要是输入数据接口)的正确实现。

这种方法的限制是您只能使用无参数构造函数配置类,但对我们来说这还不是问题。

如果您对解决方案感兴趣,我会将其上传到 github。

We had a similar case where the same features had to work for different input methods.
Our solution was similar to option #3 from Sam Holder's answer, but we wanted to use configuration files instead of code to decide which implementation to use in each case.

This could be achieved by doing our own DI mechanism. Besides being able to register implementations on runtime, it also reads a configuration file when it is first called and loads the described classes.

Now for each project which shares features with another, we have a .config file where we state the correct implementation for each shared interface (input data ones mostly).

The limitation with this approach is that you can only configure classes with parameterless constructors, but for us it has not been a problem yet.

If you are interested in the solution I will upload it to github.

老旧海报 2025-01-15 06:33:52

对我们有用的是类似于 Sam Holder 第一个选择。

  1. 在 VS 项目中引用这两个依赖项,以便将这两个 dll 复制到输出(编译)目录中。
  2. 针对不同的环境使用不同的 app.config 文件。
  3. 在每个app.config中配置正确的stepassemblies(注释/删除不需要的)

    <代码>
      <步骤组件>
        
        
      
    
    

我不需要手动 Assembly.Load调用,因为声明一个 stepAssembly 会将 NUnit 的引用作为 BindingAssembly 传递(参见 此处)。

如果我将两个程序集都声明为 stepAssemblies,则会遇到“不明确的引用”异常。

希望这会有所帮助,因为即使没有必要,共享功能也非常有帮助(例如,当您升级软件并保留功能场景测试时)。

What worked for us, was something similar to Sam Holder 1st option.

  1. Reference both dependency in your VS project so that both dll will be copied in your output (compilation) directory.
  2. use different app.config files for your different environments.
  3. in every app.config configure the right stepassemblies (comment/delete the one not needed)

    <specFlow>
      <stepAssemblies>
        <stepAssembly assembly="Tests.API" />
        <!--stepAssembly assembly="Tests.UI" /-->
      </stepAssemblies>
    </specFlow>
    

I didn't need the manual Assembly.Load call because declaring a stepAssembly will pass the reference to NUnit as a bindingAssembly (see here).

If I have both assemblies declared as stepAssemblies, I run into an "ambiguous reference" exception.

Hope this helps because sharing features is very helpful, if not necessary (when you upgrade your software and keep your functional scenario tests for example).

梦回梦里 2025-01-15 06:33:52

我认为这实际上是 3 个不同的测试,因此我的脑海中应该有 3 组功能文件。但是,如果您确实不想走这条路线,那么您可以执行诸如指定示例表之类的操作,例如

Scenario Outline: Testing app

Given I have performed a call to my service using <application>
When I do something
Then this happens

Examples:
|Application|
| web service |
| web application |
| direct call |

上述场景将运行 3 次,并传入 3 个值。该值将被传递到给定的方法(在本例中),以便您可以使用它来设置一些上下文,然后重用相同的步骤定义文件。然后,每个步骤都会知道它是要使用 Web 服务、Web 应用程序还是直接调用。

我仍然对这样做持保留态度,因为它们是单独的测试,但这是实现您想要实现的目标的一种方法。

I think that these are really 3 different tests so should have 3 sets of feature files in my mind. However if you really dont want to go down this route then you could do something like specify an examples table e.g.

Scenario Outline: Testing app

Given I have performed a call to my service using <application>
When I do something
Then this happens

Examples:
|Application|
| web service |
| web application |
| direct call |

The above scenario would run 3 times, passing in the 3 values. The value would be passed into the given method (in this case) so you could use that to setup some context and then reuse the same step definition files. Each step would then know if it was meant to use the web service, web app or direct call.

I would still have reservations about doing this as they are separate tests but it's a way to do what you want to achieve.

疯到世界奔溃 2025-01-15 06:33:52

我不确定我是否正确地考虑了您想要的内容,但这可能就是您正在寻找的内容:

Scenario Outline: Multiple approaches to test same code
    Given I am using <ApproachToCallCode>
    When I do something
    Then I expect this result

Scenarios: Approaches
    |ApproachToCallCode|
    |Internal          |
    |WebService        |
    |WebApp            |

那么,您可以在给定的方法中使用条件吗?我不确定这是否是最好的方法,但它应该有效。

I am not sure that I am thinking of what you want correctly or not, but this might be what you are looking for:

Scenario Outline: Multiple approaches to test same code
    Given I am using <ApproachToCallCode>
    When I do something
    Then I expect this result

Scenarios: Approaches
    |ApproachToCallCode|
    |Internal          |
    |WebService        |
    |WebApp            |

Then, you could use a conditional in the given method? I am not sure if this is the best approach, but it should work.

与君绝 2025-01-15 06:33:52

步骤不绑定到功能,如果步骤相同或至少其中一些步骤相同,只要措辞相同,或者您向步骤定义方法添加额外的属性,那么它们可以在其他场景或功能中重用。

根据情况,我会将它们写为额外的场景或功能。

Steps are not bound to a feature, if the steps are the same or at least some of them as long as the wording is the same or you add an extra Attribute to your step definition method then they can be reused in other scenario's or features.

Depending on the case i would either write these as extra Scenario's or Features.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文