【问题标题】:How to pass variables between cucumber-jvm steps如何在 cucumber-jvm 步骤之间传递变量
【发布时间】:2020-08-20 05:10:22
【问题描述】:

为了在步骤之间传递变量,我让步骤方法属于同一个类,并使用类的字段来传递信息。

下面是一个例子:

Feature: Demo

  Scenario: Create user
    Given User creation form management
    When Create user with name "TEST"
    Then User is created successfully

带有步骤定义的Java类:

public class CreateUserSteps {

   private String userName;

   @Given("^User creation form management$")
   public void User_creation_form_management() throws Throwable {
      // ...
   }

   @When("^Create user with name \"([^\"]*)\"$")
   public void Create_user_with_name(String userName) throws Throwable {
      //...
      this.userName = userName;
   }

   @Then("^User is created successfully$")
   public void User_is_created_successfully() throws Throwable {
      // Assert if exists an user with name equals to this.userName
   }

我的问题是,在步骤之间共享信息是否是一种好习惯?或者最好将特征定义为:

Then User with name "TEST" is created successfully

【问题讨论】:

标签: java cucumber cucumber-jvm


【解决方案1】:

为了在步骤之间共享共同点,您需要使用World。在 Java 中,它不像在 Ruby 中那样清晰。

引用 Cucumber 的创建者。

“世界”的目的是双重的:

  1. 在场景之间隔离状态。

  2. 在场景中的步骤定义和挂钩之间共享数据。

实现方式因语言而异。例如,在红宝石中, 步骤定义中的隐式 self 变量指向 当前场景的 World 对象。这是默认情况下的一个实例 对象,但如果你使用 World 钩子,它可以是任何你想要的东西。

在 Java 中,您有许多(可能连接的)World 对象。

Cucumber-Java 中 World 的等价物是 所有的对象 带有钩子或 stepdef 注释。换句话说,任何类 使用@Before、@After、@Given 等注释的方法将是 每个场景只实例化一次。

这样就实现了第一个目标。为了实现第二个目标,你有两个 方法:

a) 为所有步骤定义和挂钩使用一个类

b) 使用按职责划分的多个类 [1] 并使用依赖项 注入 [2] 将它们相互连接。

选项 a) 很快失效,因为您的步骤定义代码 变得一团糟。这就是人们倾向于使用 b) 的原因。

[1]https://cucumber.io/docs/gherkin/step-organization/

[2] PicoContainer、Spring、Guice、Weld、OpenEJB、Needle

可用的依赖注入模块有:

  • 黄瓜微型容器
  • 黄瓜酱
  • 黄瓜-openejb
  • 黄瓜春
  • 黄瓜焊缝
  • 黄瓜针

原帖在这里https://groups.google.com/forum/#!topic/cukes/8ugcVreXP0Y

希望这会有所帮助。

【讨论】:

  • 对于 Java,我使用 MyScenarioScopedData 自定义对象作为“World”,将 @RequestScoped@Inject 放入定义 Cucumber 步骤的类中。每次创建新场景时,都会创建一个新的MyScenarioScopedData
  • 那些 wiki 链接现在是悬空链接。
【解决方案2】:

可以使用实例变量在类中定义的步骤之间共享数据。如果您需要在不同类的步骤之间共享数据,您应该查看 DI 集成(PicoContainer 是最简单的)。

在您展示的示例中,我会问是否有必要在场景中显示“TEST”。用户被称为 TEST 的事实是一个偶然的细节,并且会降低场景的可读性。为什么不在 Create_user_with_name() 中生成一个随机名称(或硬编码)?

【讨论】:

  • 依赖注入如何在类之间共享状态?
  • @fijiaaron 如果您使用任何 DI 工具,例如 pico(在 pico 的情况下为构造函数注入),那么创建步骤定义和钩子类的责任将由 DI 接管。为每个场景创建这些类的新实例。此外,此步骤 defs 所需的任何其他类实例由 DI 为步骤 def 构造函数中定义的每个场景创建。然后将每个类的相同实例传递给所有需要它的步骤定义。因此,状态通过不同的步骤定义传递。
  • @fijiaaron 看看这个:stackoverflow.com/questions/34449948/… 这有帮助吗?
  • 嗨@Seb Rose。在与 cucumber-jvm 合作一年多之后,现在我相信 Pedo Lopez 的回答涵盖了更多用例。无论如何,我也认为 pico-container 可能是黄瓜的最佳 DI,但不是唯一的。这就是为什么我决定更改接受的答案的原因。再次感谢您的回答,希望您能理解。
【解决方案3】:

在纯 java 中,我只使用一个单例对象,该对象创建一次并在测试后清除。

public class TestData_Singleton {
    private static TestData_Singleton myself = new TestData_Singleton();

    private TestData_Singleton(){ }

    public static TestData_Singleton getInstance(){
        if(myself == null){
            myself = new TestData_Singleton();
        }

        return myself;
    }

    public void ClearTestData(){
        myself = new TestData_Singleton();
    }

【讨论】:

  • 使用 Cucumber 的 DI 集成的好处是您不必在每一步之前记住 ClearTestData - Cucumber 会为您处理。
  • @jjason-smiley 只是好奇你是如何实现use a Singleton object that gets created once and cleared after tests 的。我现在使用的方法是有一个带有单例的World 类,然后是一个带有@Before 注释的单个方法的WorldInitializer(因此在每个场景之前)初始化/重置单例实例
【解决方案4】:

我会说有理由在步骤之间共享信息,但我认为在这种情况下情况并非如此。如果您通过测试步骤传播用户名,那么从功能中并不清楚发生了什么。我认为最好在场景中具体说明预期的内容。我可能会这样做:

Feature: Demo

  Scenario: Create user
    Given User creation form management
    When Create user with name "TEST"
    Then A user named "TEST" has been created

然后,您的实际测试步骤可能类似于:

@When("^Create user with name \"([^\"]*)\"$")
public void Create_user_with_name(String userName) throws Throwable {
   userService.createUser(userName);
}

@Then("^A user named \"([^\"]*)\" has been created$")
public void User_is_created_successfully(String userName) throws Throwable {
   assertNotNull(userService.getUser(userName));
}

【讨论】:

  • 感谢您的意见@BarrySW19。我知道我的问题的例子不太好。但是,真的,我想知道在步骤之间共享信息是否是一种很好的做法。我会记住你的建议
【解决方案5】:

这是我的方式:我用 spring 定义了一个自定义的 Scenario-Scope 每个新场景都会有一个新的背景

Feature      @Dummy
  Scenario: zweites Scenario
   When Eins
   Then Zwei

1:使用弹簧

<properties>
<cucumber.version>1.2.5</cucumber.version>
<junit.version>4.12</junit.version>
</properties>

<!-- cucumber section -->


<dependency>
  <groupId>info.cukes</groupId>
  <artifactId>cucumber-java</artifactId>
  <version>${cucumber.version}</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>info.cukes</groupId>
  <artifactId>cucumber-junit</artifactId>
  <version>${cucumber.version}</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>junit</groupId>
  <artifactId>junit</artifactId>
  <version>${junit.version}</version>
  <scope>test</scope>
</dependency>

 <dependency> 
   <groupId>info.cukes</groupId> 
   <artifactId>cucumber-spring</artifactId> 
   <version>${cucumber.version}</version> 
   <scope>test</scope> 
 </dependency> 


<!-- end cucumber section -->

<!-- spring-stuff -->
<dependency> 
       <groupId>org.springframework</groupId> 
       <artifactId>spring-test</artifactId> 
              <version>4.3.4.RELEASE</version> 
       <scope>test</scope> 
 </dependency> 

   <dependency> 
       <groupId>org.springframework</groupId> 
       <artifactId>spring-context</artifactId> 
              <version>4.3.4.RELEASE</version> 
       <scope>test</scope>
   </dependency> 
   <dependency> 
       <groupId>org.springframework</groupId> 
       <artifactId>spring-tx</artifactId> 
       <version>4.3.4.RELEASE</version> 
       <scope>test</scope>
   </dependency> 
   <dependency> 
       <groupId>org.springframework</groupId> 
       <artifactId>spring-core</artifactId> 
       <version>4.3.4.RELEASE</version> 
       <scope>test</scope>
       <exclusions> 
           <exclusion> 
               <groupId>commons-logging</groupId> 
               <artifactId>commons-logging</artifactId> 
           </exclusion> 
       </exclusions> 
   </dependency> 
   <dependency> 
       <groupId>org.springframework</groupId> 
       <artifactId>spring-beans</artifactId> 
              <version>4.3.4.RELEASE</version> 
       <scope>test</scope>
   </dependency> 

   <dependency> 
       <groupId>org.springframework.ws</groupId> 
       <artifactId>spring-ws-core</artifactId> 
       <version>2.4.0.RELEASE</version> 
       <scope>test</scope>
   </dependency> 

2:构建自定义范围类

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(scopeName="scenario")
public class ScenarioContext {

    public Scenario getScenario() {
        return scenario;
    }

    public void setScenario(Scenario scenario) {
        this.scenario = scenario;
    }

    public String shareMe;
}

3:stepdef中的用法

@ContextConfiguration(classes = { CucumberConfiguration.class })
public class StepdefsAuskunft {

private static Logger logger = Logger.getLogger(StepdefsAuskunft.class.getName());

@Autowired
private ApplicationContext applicationContext;

// Inject service here : The impl-class need @Primary @Service
// @Autowired
// IAuskunftservice auskunftservice;


public ScenarioContext getScenarioContext() {
    return (ScenarioContext) applicationContext.getBean(ScenarioContext.class);
}


@Before
public void before(Scenario scenario) {

    ConfigurableListableBeanFactory beanFactory = ((GenericApplicationContext) applicationContext).getBeanFactory();
    beanFactory.registerScope("scenario", new ScenarioScope());

    ScenarioContext context = applicationContext.getBean(ScenarioContext.class);
    context.setScenario(scenario);

    logger.fine("Context für Scenario " + scenario.getName() + " erzeugt");

}

@After
public void after(Scenario scenario) {

    ScenarioContext context = applicationContext.getBean(ScenarioContext.class);
    logger.fine("Context für Scenario " + scenario.getName() + " gelöscht");

}



@When("^Eins$")
public void eins() throws Throwable {
    System.out.println(getScenarioContext().getScenario().getName());
    getScenarioContext().shareMe = "demo"
    // you can save servicecall here
}

@Then("^Zwei$")
public void zwei() throws Throwable {
    System.out.println(getScenarioContext().getScenario().getName());
    System.out.println(getScenarioContext().shareMe);
    // you can use last service call here
}


@Configuration
    @ComponentScan(basePackages = "i.am.the.greatest.company.cucumber")
    public class CucumberConfiguration {
    }

范围类

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;


public class ScenarioScope implements Scope {


  private Map<String, Object> objectMap = Collections.synchronizedMap(new HashMap<String, Object>());

    /** (non-Javadoc)
     * @see org.springframework.beans.factory.config.Scope#get(java.lang.String, org.springframework.beans.factory.ObjectFactory)
     */
    public Object get(String name, ObjectFactory<?> objectFactory) {
        if (!objectMap.containsKey(name)) {
            objectMap.put(name, objectFactory.getObject());
        }
        return objectMap.get(name);

    }

    /** (non-Javadoc)
     * @see org.springframework.beans.factory.config.Scope#remove(java.lang.String)
     */
    public Object remove(String name) {
        return objectMap.remove(name);
    }

    /** (non-Javadoc)
     * @see org.springframework.beans.factory.config.Scope#registerDestructionCallback(java.lang.String, java.lang.Runnable)
     */
    public void registerDestructionCallback(String name, Runnable callback) {
        // do nothing
    }

    /** (non-Javadoc)
     * @see org.springframework.beans.factory.config.Scope#resolveContextualObject(java.lang.String)
     */
    public Object resolveContextualObject(String key) {
        return null;
    }

    /** (non-Javadoc)
     * @see org.springframework.beans.factory.config.Scope#getConversationId()
     */
    public String getConversationId() {
        return "VolatileScope";
    }

    /**
     * vaporize the beans
     */
    public void vaporize() {
        objectMap.clear();
    }


}

【讨论】:

  • 为什么需要自定义 Context,Prototype 还不够吗?
  • 任何时候 Spring 进入方程复杂性真的会跳跃。 (不是海报的错,因为他遵循 Spring 的食谱。)我的意思是我们刚刚增加了两倍?代码行,它都是结构代码。不是“完成工作”的代码。这还不是全部。离机你有一个 SpringCofiguration 和 Scenario。这是一个很好的例子(虽然缺少这两部分)。但是谁愿意为了让 Cucumber 获得共享状态而经历这些苦差事呢?
【解决方案6】:

其他选项是使用 ThreadLocal 存储。创建一个上下文映射并将它们添加到映射中。 Cucumber JVM 在同一个线程中运行所有步骤,您可以在所有步骤中访问它。为了使它更容易,您可以在钩子前实例化存储并在钩子后清除。

【讨论】:

    【解决方案7】:

    如果您使用带有黄瓜的 Serenity 框架,您可以使用当前会话。

    Serenity.getCurrentSession()
    

    http://thucydides-webtests.com/2012/02/22/managing-state-between-steps/ 中有关此功能的更多信息。 (宁静以前被称为修昔底德)

    【讨论】:

      猜你喜欢
      • 2014-03-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多