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