2014-10-17 9 views
25

può passare variabili tra passi ora sto facendo qualcosa di simile l'esempio come segue:buona pratica per passare variabili tra il cetriolo-JVM passi

Feature: Demo 

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

classe Java con le definizioni gradini:

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 
    } 

La mia domanda è se questa è una buona pratica per condividere le informazioni tra i passaggi? O sarebbe meglio definire la caratteristica come:

Then User with name "TEST" is created successfully 

Sono nuovo con cetriolo-JVM quindi scusate se è una domanda senza cervello.

Qualsiasi aiuto sarebbe apprezzato. Grazie

+1

La vostra strategia funziona bene con i framework BDD in cui è possibile collegare una classe di definizioni con un file caratteristica specifica.Cucumber non supporta questo (beh, richiede uno sforzo maggiore di quello che vorresti: http://confessionsofanagilecoach.blogspot.com/2017/05/teaching-cucumbers-about-boundaries.html). Meglio usare la strategia mondiale menzionata di seguito o usare invece JBehave. –

risposta

28

Al fine di condividere in comune tra passaggi che è necessario utilizzare un World. In Java non è così chiaro come in Ruby.

Citando il creatore di Cetriolo.

Lo scopo di un "mondo" è duplice:

1) Isolare stato tra gli scenari.

2) Condividere i dati tra le definizioni di passo e gli hook all'interno di uno scenario.

Il modo in cui viene implementato è specifico per la lingua. Ad esempio, in ruby, la variabile implicita self all'interno di una definizione di passo punta all'oggetto Mondo dello scenario corrente . Questa è un'istanza predefinita di Object, ma può essere qualsiasi cosa tu voglia usare l'hook World.

In Java, ci sono molti oggetti del mondo (possibilmente connessi).

L'equivalente del mondo in Cetriolo-Java è tutti gli oggetti con annotazioni hook o stepdef. In altre parole, qualsiasi classe con i metodi annotati con @Before, @After, @Given e così via sarà istanziata esattamente una volta per ogni scenario.

Questo raggiunge il primo obiettivo. Per raggiungere il secondo obiettivo si hanno due approcci:

a) usare una singola classe per tutte le definizioni di step e ganci

b) utilizzare diverse classi suddivise per responsabilità [1] e usare la dipendenza iniezione [ 2] per collegarli tra loro.

L'opzione a) si interrompe rapidamente perché il codice di definizione step diventa un disastro. Ecco perché le persone tendono ad usare b).

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

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

I moduli iniezione di dipendenza disponibili sono:

  • cetriolo-picocontainer
  • cetriolo-guiaco
  • cetriolo-openejb
  • cetriolo-primavera
  • cetriolo-saldatura
  • cetriolo-ago

post originale qui https://groups.google.com/forum/#!topic/cukes/8ugcVreXP0Y.

Spero che questo aiuti.

+0

Grazie per il tuo aiuto @Pedro Lopez – troig

+0

Passo Il link dell'organizzazione è morto. –

+0

Aggiornato. Grazie! –

5

Va bene condividere i dati tra i passaggi definiti all'interno di una classe utilizzando una variabile di istanza. Se è necessario condividere i dati tra i passaggi di classi diverse, è necessario esaminare le integrazioni DI (PicoContainer è il più semplice).

Nell'esempio che mostri, chiederei se mostrare "TEST" nello scenario è assolutamente necessario. Il fatto che l'utente sia chiamato TEST è un dettaglio accessorio e rende lo scenario meno leggibile. Perché non generare un nome casuale (o codice hard qualcosa) in Create_user_with_name()?

+0

Grazie per la tua risposta molto utile! Approfondirò PicoContainer. – troig

+0

Come può lo stato di condivisione dell'iniezione dipendenza tra le classi? – fijiaaron

+0

@fijiaaron Se si utilizza uno strumento DI come pico (Iniezione del costruttore in caso di pico), la responsabilità della creazione di step defs e delle classi hook viene presa in carico da DI. La nuova istanza di queste classi viene creata per ogni scenario. Inoltre, qualsiasi altra istanza di classe richiesta da questo step def è creata dal DI per ogni scenario come definito nel costruttore di step def. Quindi la stessa istanza di ogni classe viene passata a tutti i step def che ne hanno bisogno. Quindi lo stato viene passato attorno a diverse definizioni di passo. – Grasshopper

2

Direi che ci sono dei motivi per condividere le informazioni tra i passaggi, ma non credo che sia il caso in questo scenario. Se si diffonde il nome utente tramite i passaggi del test, non è chiaro dalla funzione cosa sta succedendo. Penso che sia meglio dire nello specifico che cosa è previsto. Io probabilmente fare qualcosa di simile:

Feature: Demo 

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

Quindi, i tuoi passi di prova effettivi potrebbe essere simile:

@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)); 
} 
+0

Grazie per l'input @ BarrySW19. So che l'esempio della mia domanda non è troppo bello ... Ma, in realtà, volevo sapere se condividere le informazioni tra i passaggi è una buona pratica in generale. Ti tengo a mente il tuo suggerimento – troig

2

In Pure java, utilizzo solo un oggetto Singleton che viene creato una volta e cancellato dopo i test.

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

Il vantaggio di utilizzare le integrazioni DI di Cucumber è che non devi ricordarti di 'ClearTestData' prima di ogni passaggio: Cucumber lo gestisce per te. –

+0

Questo approccio ha funzionato perfettamente per me. Senza dover cambiare molto codice. +1 per puro java – Chai

2

Qui la mia strada: mi definisco uno scenario-ambito personalizzato con molla ogni nuovo scenario ci sarà un contesto fresco

Feature  @Dummy 
    Scenario: zweites Scenario 
    When Eins 
    Then Zwei 

1: Usa primavera

<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: costruire classe ambito personalizzato

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: utilizzo in s tepdef

@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 { 
    } 

classe ambito

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


} 
+0

perché hai bisogno di un contesto personalizzato, non è sufficiente Prototype? – nahab

+2

Ogni volta che Spring entra nella complessità dell'equazione salta davvero. (Non è colpa di un poster mentre seguiva la ricetta di Spring). Voglio dire, siamo appena triplicati? le linee di codice e tutto il codice della struttura. Codice non "lavoro finito". E questo non è tutto fuori. Fuori dalla fotocamera hai un SpringCofiguration e Scenario. Questo è un buon esempio (sebbene manchi quei due pezzi). Ma chi vuole passare attraverso tutto quel lavoro ingrato per fare in modo che Cucumber diventi uno stato condiviso? –

Problemi correlati