web-dev-qa-db-ja.com

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

Cucumber-jvmは初めてなので、脳みそがわからない質問であれば申し訳ありません。

任意の助けをいただければ幸いです。ありがとう

38
troig

ステップ間で共通性を共有するには、 World を使用する必要があります。 Javaでは、Rubyほど明確ではありません。

Cucumberの作成者を引用します。

「世界」の目的は2つあります。

1)シナリオ間で状態を分離します。

2)シナリオ内のステップ定義とフック間でデータを共有します。

これの実装方法は言語固有です。たとえば、Rubyでは、ステップ定義内の暗黙的なself変数は、現在のシナリオのWorldオブジェクトを指します。これはデフォルトではObjectのインスタンスですが、Worldフックを使用する場合は任意のものにできます。

Javaには、多くの(おそらく接続された)Worldオブジェクトがあります。

Cucumber-JavaのWorldに相当するものは、フックまたはstepdef注釈を持つすべてのオブジェクトです。つまり、@ Before、@ After、@ Givenなどのアノテーションが付けられたメソッドを持つクラスは、シナリオごとに1回だけインスタンス化されます。

これにより、最初の目標が達成されます。 2番目の目標を達成するには、2つのアプローチがあります。

a)すべてのステップ定義とフックに単一のクラスを使用します

b)責任[1]で分割されたいくつかのクラスを使用し、依存関係注入[2]を使用してそれらを相互に接続します。

オプションa)は、ステップ定義コードが混乱するため、すぐに壊れます。それが人々がb)を使用する傾向がある理由です。

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

[2] PicoContainer、Spring、Guice、Weld、OpenEJB、ニードル

利用可能な依存性注入モジュールは次のとおりです。

  • きゅうりピコ容器
  • きゅうりの果汁
  • きゅうりopenejb
  • きゅうり春
  • きゅうり溶接
  • きゅうり針

元の投稿はこちら https://groups.google.com/forum/#!topic/cukes/8ugcVreXP0Y

お役に立てれば。

32
Pedro Lopez

インスタンス変数を使用して、クラス内で定義されたステップ間でデータを共有しても構いません。異なるクラスのステップ間でデータを共有する必要がある場合は、DI統合を確認する必要があります(PicoContainerが最も簡単です)。

あなたが示す例では、シナリオで「TEST」を表示する必要があるかどうかを尋ねます。ユーザーがTESTと呼ばれるという事実は偶発的な詳細であり、シナリオを読みにくくします。 Create_user_with_name()でランダムな名前(またはハードコード)を生成しないのはなぜですか?

8
Seb Rose

Pure 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();
    }
3
Jason Smiley

ここで私の方法:私は、新しいシナリオが新しくなるたびに、春のカスタムシナリオスコープを定義します

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


}
2
user528322

ステップ間で情報を共有する理由があると思いますが、このシナリオではそうではないと思います。テスト手順を介してユーザー名を伝達する場合、機能から何が起こっているのかは明確ではありません。シナリオで期待されることを具体的に言う方が良いと思います。私はおそらく次のようなことをします:

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

キュウリでSerenityフレームワークを使用している場合は、現在のセッションを使用できます。

Serenity.getCurrentSession()

この機能の詳細は http://thucydides-webtests.com/2012/02/22/managing-state-between-steps/ にあります。 (以前はセレニティはトゥキュディデスと呼ばれていました)

1
bsmk

他のオプションは、ThreadLocalストレージを使用することです。コンテキストマップを作成し、マップに追加します。 Cucumber JVMはすべてのステップを同じスレッドで実行し、すべてのステップでそのスレッドにアクセスできます。簡単にするために、フックの前にストレージをインスタンス化し、フックの後にクリアすることができます。

1
samfromco