在cucumber-jvm步骤之间传递变量的好习惯

时间:2014-10-17 09:55:45

标签: java cucumber cucumber-jvm

要在步骤之间传递变量,我现在正在执行如下示例:

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

我是黄瓜jvm的新手,很抱歉,如果这是一个没脑子的问题。

任何帮助将不胜感激。感谢

7 个答案:

答案 0 :(得分:30)

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

引用黄瓜的创造者。

  

“世界”的目的有两个:

     

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

     

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

     

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

     

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

     

World in Cucumber-Java相当于所有对象   with hook或stepdef annotations 。换句话说,任何类都有   用@ Before,@ After,@ Given等注释的方法将是   每个场景只实例化一次。

     

这实现了第一个目标。为了实现第二个目标,你有两个目标   接近:

     

a)对所有步骤定义和挂钩使用单个类

     

b)使用几个按责任划分的类[1]并使用依赖   注射[2]将它们相互连接。

     

选项a)因为您的步骤定义代码而快速崩溃   变得一团糟。这就是为什么人们倾向于使用b)。

     

[1] https://github.com/cucumber/cucumber/wiki/Step-Organization

     

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

可用的依赖注入模块是:

  • 黄瓜PicoContainer的
  • 黄瓜吉斯
  • 黄瓜OpenEJB的
  • 黄瓜弹簧
  • 黄瓜焊接
  • 黄瓜针

此处的原始帖子https://groups.google.com/forum/#!topic/cukes/8ugcVreXP0Y

希望这有帮助。

答案 1 :(得分:8)

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

在您展示的示例中,我会询问是否显示" TEST"在场景中是必要的。用户称为TEST的事实是偶然的细节,使得场景的可读性降低。为什么不在Create_user_with_name()?

中生成随机名称(或硬代码)

答案 2 :(得分:3)

在Pure java中,我只使用一次创建的Singleton对象,并在测试后清除。

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();
    }

答案 3 :(得分:2)

我想说有理由在步骤之间分享信息,但我不认为这种情况就是这种情况。如果您通过测试步骤传播用户名,那么从功能中发现的内容并不是很清楚。我认为在场景中具体说明预期的结果会更好。我可能会做这样的事情:

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));
}

答案 4 :(得分:2)

我的方式:我用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();
    }


}

答案 5 :(得分:1)

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

splice

http://thucydides-webtests.com/2012/02/22/managing-state-between-steps/中有关此功能的更多信息。 (Serenity之前被称为Thucydides)

答案 6 :(得分:1)

其他选项是使用ThreadLocal存储。创建上下文映射并将其添加到地图中。 Cucumber JVM运行同一个线程中的所有步骤,您可以跨所有步骤访问它。为了简化操作,您可以在挂钩之前实例化存储,并在挂钩后清除。

相关问题