【问题标题】:How to make step argument dependent on configuration?如何使 step 参数依赖于配置?
【发布时间】:2015-06-11 23:01:12
【问题描述】:

问题

我正在使用 SpecFlow 为 REST 服务创建集成测试套件。

我正在以多种不同的配置运行该套件。 (我有多个构建配置,每个都有自己的一组 app.config 转换。)
在 C# 代码中,检查配置并根据它执行不同的代码非常简单。我可以简单地做这样的事情。

[Given(@"I set the test parameter to ""(.*)""")]
public void GivenISetTheTestParameter(string parameter)
{
    if(CurrentConfiguration == Config.Test)
        this.testParameter = parameter + "Test";
    else if(CurrentConfiguration == Config.Prod)
        this.testParameter = parameter + "Prod";
}

这种方法的问题在于它在每次执行此步骤时都以相同的方式工作,但我不想在每种情况下都以不同的方式参数化该步骤的配置相关部分。
feature 文件中有什么方法可以做到这一点吗?我想做这样的事情(伪代码,这当然行不通):

If (CurrentConfiguration == Config.Test)
Given I set the test parameter to "ParameterTest"
Else If (CurrentConfiguration == Config.Prod)
Given I set the test parameter to "ParameterProd"

然后我可以在每个场景中以不同的方式使用这个参数化:

Scenario: Test 1
    If (CurrentConfiguration == Config.Test)
    Given I set the test parameter to "ParameterTest1"
    Else If (CurrentConfiguration == Config.Prod)
    Given I set the test parameter to "ParameterProd1"
    ...

Scenario: Test 2
    If (CurrentConfiguration == Config.Test)
    Given I set the test parameter to "ParameterTest2"
    Else If (CurrentConfiguration == Config.Prod)
    Given I set the test parameter to "ParameterProd2"
    ...

如果条件是在该步骤的 C# 代码中实现的,则这是不可能的。

现实世界的例子

我想用它来集成测试 REST 服务。假设我使用基本身份验证,为此我需要在 RestClient 对象上设置一个标头。
我有一个帮助步骤,用于将 auth 标头设置为特定的用户名和密码。

棘手的部分是我有多个构建配置(比如说 Staging 和 Prod),为此我需要不同的测试凭据。另外,我在功能的不同场景中调用不同的 API,这也需要不同的凭据。

所以有了上面介绍的伪语法,这就是我想做的:

Scenario: Test LoggingService
    If (CurrentConfiguration == Config.Test)
        Given I set the auth header for the user "logging_test_user" and password "p4ssword"
    Else If (CurrentConfiguration == Config.Prod)
        Given I set the auth header for the user "logging_prod_user" and password "p4ssword"
    ...
    When I call the LoggingService
    ...

Scenario: Test PaymentService
    If (CurrentConfiguration == Config.Test)
        Given I set the auth header for the user "payment_test_user" and password "p4ssword"
    Else If (CurrentConfiguration == Config.Prod)
        Given I set the auth header for the user "payment_prod_user" and password "p4ssword"
    ...
    When I call the PaymentService
    ...

如果我只能将条件放入“鉴于我设置了身份验证标头...”步骤的 C# 实现中,那么我将无法为不同的场景指定不同的用户名。

【问题讨论】:

  • 您能否提供更多背景信息以及一个类似真实世界的示例?我很难理解为什么你需要把它放在功能文件中而不是直接从System.Configuration.ConfigurationManager 读取它?
  • 添加了另一个示例,说明拥有条件特征文件的优势。
  • 我正在寻找一个真实的用例场景来说明您为什么需要它。您说您正在测试 REST Web 服务。 App.config 中的哪些数据发生变化,需要您以不同的方式测试服务?
  • 我想我想说的是,“为什么”你想这样做?我有一种感觉,您的问题的解决方案与您想象的大不相同。
  • 添加了一个真实世界的例子。

标签: c# specflow gherkin


【解决方案1】:

您根本不需要功能文件中的可配置数据。相反,创建一个通用步骤,其定义读取配置文件:

Scenario: Test LoggingService
    Given I set the auth header

在 C# 中:

[Given(@"I set the auth header")]
public void GivenISetTheAuthHeader()
{
    string username = System.Configuration.ConfigurationManager.AppSettings["RestServiceUserName"];
    string password = System.Configuration.ConfigurationManager.AppSettings["RestServicePassword"];
}

在 App.config 中:

<appSettings>
  <add key="RestServiceUserName" value="..."/>
  <add key="RestServicePassword" value="..."/>

如果不同的用户名在系统中有不同的权限,那么考虑使用场景大纲来代替:

Scenario Outline: Testing the LoggingService
    Given I set the auth header for user "<Username>" and password "<Password>"

Examples:
    | Username | Password |
    | user1    | pass1    |
    | user2    | pass2    |

它们成为您步骤定义的常规参数:

[Given("I set the auth header for user """(.*)""" and password """(.*)"""")]
public void GivenISetTheAuthHeaderForUserAndPassword(string username, string password)
{
    // set the user and password on the auth header
}

【讨论】:

    【解决方案2】:

    您可以通过场景大纲和标记示例来实现您想要的,但是您只需要在某些环境中运行一些测试:

    Scenario Outline: Testing the LoggingService
        Given I set the auth header for user "<Username>" and password "<Password>"
    
    @Production
    Examples:
        | Username          | Password |
        | logging_prod_user | p4ssword |
    @Test
    Examples:
        | Username          | Password  |
        | logging_test_user | p4assword |
    

    然后将您的测试运行程序配置为仅运行特定类别的测试(TestProduction

    如果您使用 nunit(或 XUnit 或任何其他默认使用行测试来运行场景大纲的测试运行器)作为测试运行器,请注意 this issue

    【讨论】:

      【解决方案3】:

      我们为不同的环境做了类似的事情,但是我们有一个 app.config 用于测试,它有几个用于 dev、qa 和 uat 的“替代”配置,我们从这些部分之一读取命名参数的值。

      我们有这样的东西

      <testingEnvironments>
        <testingEnvironment name="Dev" messageUrl="https://somedevurl/" isCurrent="true">
           <ConfigParam1>SomeValue</ConfigParam1>
        </testingEnvironment>
        <testingEnvironment name="QA" messageUrl="https://somedqaurl/" isCurrent="false">
           <ConfigParam1>SomeValueInQA</ConfigParam1>
        </testingEnvironment>
        <testingEnvironment name="UAT" messageUrl="https://someuaturl/" isCurrent="false">
           <ConfigParam1>SomeValueUAT</ConfigParam1>
        </testingEnvironment>
      </testingEnvironments>
      

      我们根据isCurrent 属性的值选择配置,但您可以根据环境变量在名称上选择它。

      那么您的测试不知道使用的确切值然后只需参考ConfigParam1

      基于您的真实示例,我不喜欢测试中的实现细节(如果您使用其他一些身份验证机制会怎样)并且会像这样重组我的规范:

      Scenario: Test LoggingService
          Given I am the logging service default user for the current environment     
          When I call the LoggingService
          ...
      
      Scenario: Test payment Service
          Given I am the payment service default user for the current environment     
          When I call the PaymentService
          ...
      

      并且会添加类似这样的配置:

        <testingEnvironment name="DEV" messageUrl="https://somedevurl/" isCurrent="false">
           <userCredentials>
                <LoggingService>
                   <defaultUser name="logging_test_user" password="p4ssword" />                 
                </LoggingService>
                <PaymentService>
                   <defaultUser name="payment_test_user" password="p4ssword" />                 
                </PaymentService>
           </userCredentials>         
        </testingEnvironment>
        <testingEnvironment name="UAT" messageUrl="https://someuaturl/" isCurrent="false">
           <userCredentials>
                <LoggingService>
                   <defaultUser name="logging_prod_user" password="p4ssword" />                 
                </LoggingService>
                <PaymentService>
                   <defaultUser name="payment_prod_user" password="p4ssword" />                 
                </PaymentService>
           </userCredentials>         
        </testingEnvironment>
      

      然后您的各个步骤可以调用一个通用步骤来设置实际的标头值

      【讨论】:

        【解决方案4】:

        您的测试应该总是相同的——一个带有“if”的测试至少是两个测试。解决这个问题的正确方法是隔离被测系统,使其接受一个代表配置值的参数(或以其他方式提供一个值),然后为所有适用场景编写测试。

        【讨论】:

          【解决方案5】:

          我会写功能:

          Scenario: Test LoggingService
              Given I set the auth header with valid user and password
              When I call the LoggingService
              # ...
          

          设置App.config文件:

          <appSettings>
              <add key="EnvironmentUserName" value="..."/>
              <add key="EnvironmentPassword" value="..."/>
              <!-- ... -->
          </appSettings>
          

          并将步骤实现为:

          public void GivenISetTheAuthHeader()
          {
              string username = System.Configuration.ConfigurationManager.AppSettings["EnvironmentUserName"];
              string password = System.Configuration.ConfigurationManager.AppSettings["EnvironmentPassword"];
              // ...
           }
          

          【讨论】:

          • 是的,我已经以不同的方式执行此操作,但是这样凭据不能取决于您从哪个场景调用该步骤,因此您必须实施两个不同的步骤。跨度>
          猜你喜欢
          • 2023-03-14
          • 1970-01-01
          • 2014-07-19
          • 2012-01-22
          • 2013-11-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多