Хорошая практика передачи переменных между шагами cucumber-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
Я новичок с огурцом-JVM так жаль, если это безмозглый вопрос.
любая помощь будет оценили. Спасибо
7 ответов
чтобы разделить общие черты между шагами, вам нужно использовать мир. В Java это не так ясно, как в Ruby.
цитируя создателя огурца.
цель "мира" двоякая:
1) изолировать состояние между сценариями.
2) обмен данными между определениями шагов и крючками в сценарии.
Как это реализовано, зависит от языка. Например, в ruby, этот неявное
self
переменная внутри определения шага указывает на объект мира текущего сценария. По умолчанию это экземпляр Объект, но это может быть все, что вы хотите, если вы используете World hook.в Java, у вас есть много (возможно, связанных) объектов.
эквивалент мира в Cucumber-Java является все объекты с крючком или stepdef аннотации. Другими словами, любой класс с методы, аннотированные @Before, @After, @Дано и так далее будет инстанцирован один раз для каждого сценария.
Это достигает первой цели. Для достижения второй цели у вас есть два подходы:
a) используйте один класс для всех ваших определений шагов и крючков
b) используйте несколько классов, разделенных по ответственности [1] и используйте зависимость инъекции [2] для соединения их друг с другом.
опция a) быстро ломается, потому что код определения шага становится беспорядок. Вот почему люди склонны использовать b).
[1] https://github.com/cucumber/cucumber/wiki/Step-Organization
[2] PicoContainer, Весна, Guice, Сварка, OpenEJB, Игла
доступные модули инъекций зависимостей:
- огурец-picocontainer
- огурец-guice
- огурец-openejb
- огурец-весна
- огурец-сварки
- огурец-игла
Оригинальный пост здесь https://groups.google.com/forum#!тема/cukes/8ugcVreXP0Y.
надеюсь, что это помогает.
можно обмениваться данными между шагами, определенными в классе, используя переменную экземпляра. Если вам нужно обмениваться данными между шагами в разных классах, вы должны посмотреть на интеграции DI (PicoContainer является самым простым).
в Примере, который вы показываете, я бы спросил, нужно ли вообще показывать "тест" в сценарии. Тот факт, что пользователь называется TEST, является случайной деталью и делает сценарий менее читаемым. Почему бы не создать случайное имя (или жесткий код что-то) в Create_user_with_name()?
в чистой java я просто использую одноэлементный объект, который создается один раз и очищается после тестов.
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();
}
Я бы сказал, что есть причины для обмена информацией между шагами, но я не думаю, что дело в этом случае. Если вы распространяете имя пользователя через шаги теста, то из функции не совсем ясно, что происходит. Я думаю, что лучше конкретно сказать в сценарии, что ожидается. Я, вероятно, сделал бы что-то вроде этого:
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));
}
вот мой путь: я определяю пользовательский сценарий-область с 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();
}
}
Если вы используете Serenity framework с огурцом, Вы можете использовать текущий сеанс.
Serenity.getCurrentSession()
подробнее об этой функции в http://thucydides-webtests.com/2012/02/22/managing-state-between-steps/. (Раньше безмятежность называлась Фукидидом)--2-->
другой вариант - использовать хранилище ThreadLocal. Создайте контекстную карту и добавьте их на карту. Cucumber JVM выполняет все шаги в одном потоке, и у вас есть доступ к этому через все шаги. Чтобы сделать это проще, вы можете создать экземпляр хранилища до крючка и очистить после крючка.