簡體   English   中英

如何在黃瓜 jvm 步驟之間傳遞變量

[英]How to pass variables between cucumber-jvm steps

為了在步驟之間傳遞變量,我讓步驟方法屬於同一個類,並使用類的字段作為傳遞的信息。

下面是一個例子:

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

為了在步驟之間共享共性,您需要使用World 在 Java 中,它不像在 Ruby 中那樣清晰。

引用 Cucumber 的創造者。

“世界”的目的有兩個:

  1. 隔離場景之間的狀態。

  2. 在場景中的步驟定義和掛鈎之間共享數據。

這是如何實現的是特定於語言的。 例如,在 ruby​​ 中,步驟定義中的隱式self變量指向當前場景的 World 對象。 默認情況下,這是 Object 的一個實例,但如果您使用 World 鈎子,它可以是您想要的任何東西。

在 Java 中,您有許多(可能連接的) World 對象。

Cucumber-Java 中 World 的等價物是所有帶有 hook 或 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

可用的依賴注入模塊有:

  • 黃瓜-picocontainer
  • 黃瓜-guice
  • 黃瓜-openejb
  • 黃瓜春天
  • 黃瓜焊接
  • 黃瓜針

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

希望這可以幫助。

可以使用實例變量在類中定義的步驟之間共享數據。 如果您需要在不同類中的步驟之間共享數據,您應該查看 DI 集成(PicoContainer 是最簡單的)。

在您展示的示例中,我會問是否有必要在場景中顯示“TEST”。 用戶被稱為 TEST 的事實是一個附帶的細節,這會降低場景的可讀性。 為什么不在 Create_user_with_name() 中生成隨機名稱(或硬編碼)?

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

我會說有理由在步驟之間共享信息,但我認為在這種情況下情況並非如此。 如果您通過測試步驟傳播用戶名,那么該功能並不清楚發生了什么。 我認為最好在場景中具體說明預期的內容。 我可能會做這樣的事情:

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

這是我的方式:我用 spring 定義了一個自定義的場景范圍,每個新場景都會有一個新的上下文

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


}

如果您使用帶有黃瓜的 Serenity 框架,則可以使用當前會話。

Serenity.getCurrentSession()

有關此功能的更多信息,請訪問http://thucydides-webtests.com/2012/02/22/managing-state-between-steps/ (寧靜以前叫修昔底德)

另一種選擇是使用 ThreadLocal 存儲。 創建上下文映射並將它們添加到映射中。 Cucumber JVM 在同一個線程中運行所有步驟,您可以在所有步驟中訪問它。 為了更容易,您可以在鈎子之前實例化存儲並在鈎子之后清除。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM