web-dev-qa-db-fra.com

Bonne pratique pour passer des variables entre les étapes concombre-jvm

Pour passer des variables entre les étapes maintenant, je fais quelque chose comme l’exemple suivant:

Feature: Demo

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

Classe Java avec définitions d'étapes:

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
   }

Ma question est de savoir s'il s'agit d'une bonne pratique de partager des informations entre les étapes. Ou serait mieux définir la fonctionnalité comme suit:

Then User with name "TEST" is created successfully

Je suis nouveau avec concombre-jvm donc désolé si c'est une question sans cervelle.

Toute aide serait appréciée. Merci

33
troig

Afin de partager les points communs entre les étapes, vous devez utiliser un World . En Java, ce n'est pas aussi clair qu'en Ruby.

Citant le créateur de concombre.

Le but d'un "monde" est double:

1) Isoler l'état entre les scénarios.

2) Partagez des données entre les définitions d'étape et les points d'ancrage d'un scénario.

La façon dont cela est mis en œuvre est spécifique à la langue. Par exemple, dans Ruby, la variable implicite self dans une définition d'étape pointe vers le Objet World du scénario actuel. Ceci est par défaut une instance de Objet, mais cela peut être ce que vous voulez si vous utilisez le crochet Monde.

En Java, vous avez de nombreux objets World (éventuellement connectés).

L'équivalent du Monde en Cucumber-Java est tous les objets avec des annotations hook ou stepdef. En d'autres termes, toute classe avec Les méthodes annotées avec @Before, @After, @Given et ainsi de suite seront instancié exactement une fois pour chaque scénario.

Ceci réalise le premier objectif. Pour atteindre le deuxième objectif, vous avez deux approches:

a) Utilisez une seule classe pour toutes vos définitions d'étape et vos crochets

b) Utilise plusieurs classes divisées par responsabilité [1] et utilise la dépendance injection [2] pour les relier les uns aux autres.

L'option a) se décompose rapidement car votre code de définition d'étape devient un désordre. C'est pourquoi les gens ont tendance à utiliser b).

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

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

Les modules d'injection de dépendance disponibles sont les suivants:

  • concombre-picocontainer
  • concombre-guice
  • concombre-openejb
  • concombre printemps
  • concombre-soudure 
  • concombre-aiguille

Article original ici https://groups.google.com/forum/#!topic/cukes/8ugcVreXP0Y .

J'espère que cela t'aides.

30
Pedro Lopez

C'est bien de partager des données entre les étapes définies dans une classe à l'aide d'une variable d'instance. Si vous avez besoin de partager des données entre des étapes de classes différentes, vous devez examiner les intégrations DI (PicoContainer est le plus simple).

Dans l'exemple que vous montrez, je vous demanderais s'il est nécessaire d'afficher "TEST" dans le scénario. Le fait que l'utilisateur s'appelle TEST est un détail accessoire et rend le scénario moins lisible. Pourquoi ne pas générer un nom aléatoire (ou quelque chose de code dur) dans Create_user_with_name ()?

6
Seb Rose

En Java pur, je viens d'utiliser un objet Singleton qui est créé une fois et effacé après les tests.

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

Voici mon chemin: je définis un scénario-Scope personnalisé avec spring Chaque nouveau scénario, il y aura un nouveau contexte

Feature      @Dummy
  Scenario: zweites Scenario
   When Eins
   Then Zwei

1: Utilisez le printemps 

<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: construire une classe de portée personnalisée

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: utilisation dans 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 {
    }

la classe de portée

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

Je dirais qu'il y a des raisons de partager des informations entre les étapes, mais je ne pense pas que ce soit le cas dans ce scénario. Si vous propagez le nom d'utilisateur via les étapes de test, la fonctionnalité ne permet pas de savoir exactement ce qui se passe. Je pense qu'il est préférable de spécifier spécifiquement dans le scénario ce qui est attendu. Je ferais probablement quelque chose comme ça:

Feature: Demo

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

Ensuite, vos étapes de test réelles pourraient ressembler à ceci:

@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

Si vous utilisez le framework Serenity avec un concombre, vous pouvez utiliser la session en cours.

Serenity.getCurrentSession()

plus d'informations sur cette fonctionnalité dans http://thucydides-webtests.com/2012/02/22/managing-state-between-steps/ . (Serenity s'appelait auparavant Thucydides)

1
bsmk

Une autre option consiste à utiliser le stockage ThreadLocal. Créez une carte de contexte et ajoutez-les à la carte. La JVM de concombre exécute toutes les étapes dans le même thread et vous y avez accès à travers toutes les étapes. Pour faciliter les choses, vous pouvez instancier le stockage dans avant crochet et effacer dans après crochet. 

1
samfromco