2014-10-17 10 views
25

Um Variablen zwischen den Schritten passieren nun so etwas wie das Beispiel, das ich mache, wie folgt:Good Practice Variablen zwischen Gurken-jvm passieren Schritte

Feature: Demo 

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

Java-Klasse mit den Schritten Definitionen:

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 
    } 

Meine Frage ist, ob dies eine gute Praxis ist, Informationen zwischen den Schritten zu teilen? Oder wäre besser definieren die Funktion als:

Then User with name "TEST" is created successfully 

Ich bin neu mit Gurken-jvm so leid, wenn es sich um eine hirnlose Frage ist.

Jede Hilfe wäre willkommen. Danke

+1

Ihre Strategie gut mit BDD-Frameworks funktioniert, wo Sie eine Klasse von Definitionen mit einer bestimmten Funktion Datei verknüpfen.Gurke unterstützt dies nicht (nun, erfordert mehr Aufwand als Sie möchten: http://confessionsofanagilecoach.blogspot.com/2017/05/teaching-cucumbers-about-boundaries.html). Verwenden Sie besser die unten genannte World-Strategie oder verwenden Sie JBehave. –

Antwort

28

Um Gemeinsamkeiten zwischen den Schritten teilen Sie ein World verwenden müssen. In Java ist es nicht so klar wie in Ruby.

Zitat der Schöpfer der Gurke.

Der Zweck einer "Welt" ist eine doppelte:

1) Zustand zwischen den Szenarien isolieren.

2) Teilen Sie Daten zwischen Schrittdefinitionen und Hooks innerhalb eines Szenarios.

Wie dies implementiert wird, ist sprachspezifisch. Zum Beispiel in Ruby, die implizite self Variable innerhalb einer Schrittdefinition zeigt auf das aktuelle Szenario World-Objekt. Dies ist standardmäßig eine Instanz von Object, aber es kann alles sein, was Sie wollen, wenn Sie den World Hook verwenden.

In Java haben Sie viele (möglicherweise verbundene) World-Objekte.

Das Äquivalent der Welt in Cucumber-Java ist alle Objekte mit Hook oder Stepdef Annotationen. Mit anderen Worten, jede Klasse mit Methoden, die mit @Before, @After, @Given usw. versehen sind, wird genau einmal für jedes Szenario instanziiert.

Dies erreicht das erste Ziel. das zweite Ziel zu erreichen, haben Sie zwei Ansätze:

a) für alle Ihre Schritt Definitionen eine einzige Klasse verwenden und Haken

b) mehrere Klassen von Verantwortung geteilt Verwenden Sie [1] und die Abhängigkeit Injektion verwenden [ 2] um sie miteinander zu verbinden.

Option a) bricht schnell zusammen, weil Ihr Schrittdefinitionscode ein Chaos wird. Deshalb neigen Leute dazu, b) zu benutzen.

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

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

Die verfügbaren Injection Dependency Module sind:

  • cucumber-picocontainer
  • Gurken-Guice
  • Gurke-Openejb
  • Gurken-Feder
  • Gurken-Schweiß
  • Gurken-Nadel

Original-Beitrag hier https://groups.google.com/forum/#!topic/cukes/8ugcVreXP0Y.

Hoffe, das hilft.

+0

Vielen Dank für Ihre Hilfe @Pedro Lopez – troig

+0

Schritt Organisation Link ist tot. –

+0

Aktualisiert. Vielen Dank! –

5

Es ist in Ordnung, Daten zwischen Schritten innerhalb einer Klasse mit einer Instanzvariable zu teilen. Wenn Sie Daten zwischen Schritten in verschiedenen Klassen teilen müssen, sollten Sie sich die DI-Integrationen ansehen (PicoContainer ist die einfachste).

In dem Beispiel, das Sie zeigen, würde ich fragen, ob überhaupt "TEST" im Szenario angezeigt wird. Die Tatsache, dass der Benutzer TEST genannt wird, ist ein nebensächliches Detail und macht das Szenario weniger lesbar. Warum nicht einen zufälligen Namen (oder harten Code etwas) in Create_user_with_name() erzeugen?

+0

Danke für Ihre sehr hilfreiche Antwort! Ich werde tief in PicoContainer. – troig

+0

Wie kann die Abhängigkeitsinjektion den Status zwischen Klassen teilen? – fijiaaron

+0

@fijiaaron Wenn Sie ein beliebiges DI-Tool wie Pico (Konstruktorinjektion bei Pico) verwenden, wird die Verantwortung für das Erstellen von Step-Defs und Hook-Klassen von DI übernommen. Eine neue Instanz dieser Klassen wird für jedes Szenario erstellt. Außerdem werden alle anderen Klasseninstanzen, die für diese Schrittdefinitionen erforderlich sind, von der DI für jedes Szenario erstellt, wie im Konstruktor von step def definiert. Dann wird die gleiche Instanz jeder Klasse an alle Schrittdefinitionen übergeben, die sie benötigen. Somit wird der Zustand um verschiedene Schrittdefinitionen herumgereicht. – Grasshopper

2

Ich würde sagen, dass es Gründe gibt, Informationen zwischen den Schritten zu teilen, aber ich denke nicht, dass das in diesem Szenario der Fall ist. Wenn Sie den Benutzernamen über die Testschritte weitergeben, ist es nicht wirklich klar aus dem Feature, was vor sich geht. Ich denke, es ist besser, im Szenario konkret zu sagen, was erwartet wird. Ich würde wahrscheinlich etwas tun:

Feature: Demo 

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

Dann Ihre eigentliche Prüfschritte könnte etwa so aussehen:

@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

Vielen Dank für Ihre Eingabe @ BarrySW19. Ich weiß, dass das Beispiel meiner Frage nicht so gut ist. Aber ich wollte wirklich wissen, ob ich Informationen zwischen den Schritten teilen sollte. Ich werde Ihren Vorschlag im Hinterkopf behalten – troig

2

In reinem Java verwende ich nur ein Singleton-Objekt, das einmal erstellt und nach Tests gelöscht wird.

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

Der Vorteil von Cucumbers DI-Integrationen ist, dass Sie sich nicht vor jedem Schritt an 'ClearTestData' erinnern müssen - Gucumber erledigt das für Sie. –

+0

Dieser Ansatz funktionierte perfekt für mich. Ohne viel Code ändern zu müssen. +1 für reines Java – Chai

2

Hier ist meine Art und Weise: Ich definiere einen benutzerdefinierten Szenario-Scope mit Feder jedes neue Szenario gibt es einen frischen Kontext sein

Feature  @Dummy 
    Scenario: zweites Scenario 
    When Eins 
    Then Zwei 

1: Verwenden Sie Feder

<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: bauen benutzerdefinierten Bereich Klasse

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: Nutzung 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 { 
    } 

der Umfang Klasse

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

Warum brauchen Sie benutzerdefinierten Kontext, ist nicht Prototype genug? – nahab

+2

Immer wenn der Frühling in die Gleichung eintritt, springt die Komplexität wirklich hoch. (Nicht die Schuld des Posters, als er Spring's Rezept folgte.) Ich meine, wir haben uns gerade verdreifacht? die Codezeilen und alles Strukturcode. Nicht "Arbeit machen" -Code. Und das ist noch nicht alles. Off-Kamera haben Sie eine SpringCFiguration und Szenario. Dies ist ein gutes Beispiel (obwohl diese beiden Teile fehlen). Aber wer möchte die ganze Plackerei durchmachen, um die Gurke zum gemeinsamen Staat zu machen? –

Verwandte Themen