Подтвердить что ты не робот

Хорошая практика передачи переменных между шагами огурца-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, так что извините, если это безмозглый вопрос.

Любая помощь будет оценена по достоинству. Благодаря

4b9b3361

Ответ 1

Чтобы разделить общие черты между шагами, вам нужно использовать World. В Java это не так ясно, как в Ruby.

Цитирование создателя огурца.

Цель "Мира" двояка:

1) Изолировать состояние между сценариями.

2) Делитесь данными между определениями шагов и крючками внутри сценария.

Как это реализовано, зависит от языка. Например, в рубине, неявная переменная self внутри определения шага указывает на текущий сценарий Объект мира. Это по умолчанию экземпляр Объект, но он может быть любым, если вы используете крюк "Мир".

В Java у вас есть много (возможно связанных) объектов World.

Эквивалент мира в Cucumber-Java - это все объекты с аннотациями hook или stepdef. Другими словами, любой класс с методы, аннотированные с @Before, @After, @Given и т.д. будут созданный один раз для каждого сценария.

Это достигает первой цели. Для достижения второй цели у вас есть два подходы:

a) Используйте один класс для всех ваших определений шагов и перехватов

b) Используйте несколько классов, разделенных ответственностью [1], и используйте зависимость инъекции [2], чтобы соединить их друг с другом.

Вариант а) быстро ломается, потому что код определения шага становится беспорядком. Вот почему люди склонны использовать б).

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

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

Доступные модули впрыска для зависимостей:

  • огурца PicoContainer
  • огурца Guice
  • огурца OpenEJB
  • cucumber- spring
  • огурец-сварка
  • огурец-игла

Оригинальная запись здесь https://groups.google.com/forum/#!topic/cukes/8ugcVreXP0Y.

Надеюсь, что это поможет.

Ответ 2

Хорошо делиться данными между шагами, определенными внутри класса, используя переменную экземпляра. Если вам нужно обмениваться данными между шагами в разных классах, вы должны посмотреть на интеграцию DI (PicoContainer - самый простой).

В примере, который вы показываете, я бы спросил, нужно ли вообще показывать "ТЕСТ" в сценарии. Тот факт, что пользователь называется TEST, является случайной деталью и делает сценарий менее читаемым. Почему бы не создать случайное имя (или жесткий код что-то) в Create_user_with_name()?

Ответ 3

В Pure java я просто использую объект Singleton, который создается один раз и очищается после тестов.

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

Ответ 4

Я бы сказал, что есть причины для обмена информацией между шагами, но я не думаю, что случай в этом сценарии. Если вы распространяете имя пользователя с помощью тестовых шагов, это не совсем ясно из функции, что происходит. Я думаю, что лучше сказать в сценарии, что ожидается. Я бы, наверное, сделал что-то вроде этого:

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

Ответ 5

Здесь мой способ: я определяю пользовательский Scenario-Scope с spring каждый новый сценарий будет иметь свежий контекст

Feature      @Dummy
  Scenario: zweites Scenario
   When Eins
   Then Zwei

1: Используйте spring

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


}

Ответ 6

Если вы используете платформу Serenity с огурцом, вы можете использовать текущий сеанс.

Serenity.getCurrentSession()

Подробнее об этой функции в http://thucydides-webtests.com/2012/02/22/managing-state-between-steps/. (Serenity назывался Thucydides раньше)

Ответ 7

Другой вариант - использовать хранилище ThreadLocal. Создайте контекстную карту и добавьте их на карту. Cuvumber JVM запускает все шаги в одном потоке, и у вас есть доступ к этому на всех этапах. Чтобы сделать это проще, вы можете создать экземпляр хранилища до и после него.

Ответ 8

В моем случае первым сценарием теста было получение токена доступа. Мне удалось передать токен с помощью статической переменной в том же классе шагов.