Java 在黄瓜 jvm 步骤之间传递变量的良好做法
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/26422470/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Good practice to pass variables between cucumber-jvm steps
提问by troig
To pass variables between steps now I'm doing something like the example as follows:
现在,为了在步骤之间传递变量,我正在执行类似示例的操作,如下所示:
Feature: Demo
Scenario: Create user
Given User creation form management
When Create user with name "TEST"
Then User is created successfully
Java class with steps definitions:
带有步骤定义的 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
}
My question is if it is a good practice to share information between steps? Or would be better to define the feature as:
我的问题是在步骤之间共享信息是否是一个好习惯?或者最好将该功能定义为:
Then User with name "TEST" is created successfully
采纳答案by Pedro Lopez
In order to share commonalities between steps you need to use a World. In Java it is not as clear as in Ruby.
为了在步骤之间共享共性,您需要使用World。在 Java 中,它不像在 Ruby 中那样清晰。
Quoting the creator of Cucumber.
引用 Cucumber 的创造者。
The purpose of a "World" is twofold:
1) Isolate state between scenarios.
2) Share data between step definitions and hooks within a scenario.
How this is implemented is language specific. For example, in ruby, the implicit
self
variable inside a step definition points to the current scenario's World object. This is by default an instance of Object, but it can be anything you want if you use the World hook.In Java, you have many (possibly connected) World objects.
The equivalent of the World in Cucumber-Java is all of the objects with hook or stepdef annotations. In other words, any class with methods annotated with @Before, @After, @Given and so on will be instantiated exactly once for each scenario.
This achieves the first goal. To achieve the second goal you have two approaches:
a) Use a single class for all of your step definitions and hooks
b) Use several classes divided by responsibility [1] and use dependency injection [2] to connect them to each other.
Option a) quickly breaks down because your step definition code becomes a mess. That's why people tend to use b).
[1] https://github.com/cucumber/cucumber/wiki/Step-Organization
[2] PicoContainer, Spring, Guice, Weld, OpenEJB, Needle
“世界”的目的有两个:
1)在场景之间隔离状态。
2) 在场景中的步骤定义和挂钩之间共享数据。
这是如何实现的是特定于语言的。例如,在 ruby 中,
self
步骤定义中的隐式变量指向当前场景的 World 对象。默认情况下,这是 Object 的一个实例,但如果您使用 World 钩子,它可以是您想要的任何东西。在 Java 中,您有许多(可能连接的) World 对象。
Cucumber-Java 中 World 的等价物是所有带有 hook 或 stepdef 注释的对象。换句话说,任何带有@Before、@After、@Given 等注释的方法的类都将在每个场景中被实例化一次。
这样就达到了第一个目标。要实现第二个目标,您有两种方法:
a) 为所有步骤定义和挂钩使用单个类
b) 使用按职责划分的几个类[1],并使用依赖注入[2]将它们相互连接起来。
选项 a) 很快就会崩溃,因为您的步骤定义代码变得一团糟。这就是为什么人们倾向于使用 b)。
[1] https://github.com/cucumber/cucumber/wiki/Step-Organization
[2] PicoContainer、Spring、Guice、Weld、OpenEJB、Needle
The available Dependency Injection modules are:
可用的依赖注入模块有:
- cucumber-picocontainer
- cucumber-guice
- cucumber-openejb
- cucumber-spring
- cucumber-weld
- cucumber-needle
- 黄瓜-picocontainer
- 黄瓜-guice
- 黄瓜-openejb
- 黄瓜春天
- 黄瓜焊接
- 黄瓜针
Original post here https://groups.google.com/forum/#!topic/cukes/8ugcVreXP0Y.
原始帖子在这里https://groups.google.com/forum/#!topic/cukes/8ugcVreXP0Y。
Hope this helps.
希望这可以帮助。
回答by Seb Rose
It's fine to share data between steps defined within a class using an instance variable. If you need to share data between steps in different classes you should look at the DI integrations (PicoContainer is the simplest).
可以使用实例变量在类中定义的步骤之间共享数据。如果您需要在不同类中的步骤之间共享数据,您应该查看 DI 集成(PicoContainer 是最简单的)。
In the example you show, I'd ask whether showing "TEST" in the scenario is necessary at all. The fact that the user is called TEST is an incidental detail and makes the scenario less readable. Why not generate a random name (or hard code something) in Create_user_with_name()?
在您展示的示例中,我会问是否有必要在场景中显示“TEST”。用户被称为 TEST 的事实是一个附带的细节,这会降低场景的可读性。为什么不在 Create_user_with_name() 中生成一个随机名称(或硬编码)?
回答by BarrySW19
I would say that there are reasons to share information between steps, but I don't think that's the case in this scenario. If you propagate the user name via the test steps then it's not really clear from the feature what's going on. I think it's better to specifically say in the scenario what is expected. I would probably do something like this:
我会说有理由在步骤之间共享信息,但我认为在这种情况下情况并非如此。如果您通过测试步骤传播用户名,那么该功能并不清楚发生了什么。我认为最好在场景中具体说明预期的内容。我可能会做这样的事情:
Feature: Demo
Scenario: Create user
Given User creation form management
When Create user with name "TEST"
Then A user named "TEST" has been created
Then, your actual test steps might look something like:
然后,您的实际测试步骤可能类似于:
@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));
}
回答by Jason Smiley
In Pure java, I just use a Singleton object that gets created once and cleared after tests.
在纯 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();
}
回答by bsmk
If you are using Serenity framework with cucumber you can use current session.
如果您使用带有黄瓜的 Serenity 框架,则可以使用当前会话。
Serenity.getCurrentSession()
more about this feature in http://thucydides-webtests.com/2012/02/22/managing-state-between-steps/. (Serenity was called Thucydides before)
有关此功能的更多信息,请访问http://thucydides-webtests.com/2012/02/22/managing-state-between-steps/。(宁静以前叫修昔底德)
回答by user528322
Here my way: I define a custom Scenario-Scope with spring every new scenario there will be a fresh context
这是我的方式:我用 spring 定义了一个自定义的场景范围,每个新场景都会有一个新的上下文
Feature @Dummy
Scenario: zweites Scenario
When Eins
Then Zwei
1: Use spring
1:使用弹簧
<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: build custom scope class
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: usage in stepdef
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 {
}
the scope class
范围类
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();
}
}
回答by samfromco
Other option is to use ThreadLocal storage. Create a context map and add them to the map. Cucumber JVM runs all the steps in the same thread and you have access to that across all the steps. To make it easier, you can instantiate the storage in before hook and clear in after hook.
另一种选择是使用 ThreadLocal 存储。创建上下文映射并将它们添加到映射中。Cucumber JVM 在同一个线程中运行所有步骤,您可以在所有步骤中访问它。为了更容易,您可以在钩子之前实例化存储并在钩子之后清除。