在 Cucumber 中,是否可以通过编程方式获取正在执行的当前步骤?
In Cucumber, is it possible to programmatically get the current step being executed?
Scenario: As a user, I want to login to the system
Given I am on my website
When I enter valid credentials
Then I am taken to the home page
可以使用getName()
函数检索场景名称。有没有办法让正在执行的步骤(在 Java
中)?我们预见到在日志记录和报告中使用它。
因此,对于上述情况,在执行相应的步骤定义时将返回 I am on my website
。
您是在询问是否可以获得一些表明步骤 When I enter valid credentials
已执行的日志记录?
如果是,答案是肯定的。
Cucumber 本身没有日志记录的概念,因此您必须添加自己喜欢的日志记录框架。由于 Cucumber 不知道如何通过您最喜欢的日志框架进行日志记录,因此您必须在 Java.
中实现的每个步骤中添加一条日志语句
我从未见过自己需要登录。来自 Maven 的执行日志,或者您使用的任何构建工具,已经足够我使用很长时间了。
报告包括已执行的步骤,以便涵盖案例。
作为新手,我不允许发表评论,所以这里有一些信息,假设您使用的是 cucumber-jvm。
简短回答,不,Cucumber 本身没有读取步骤名称的选项。您可以使用方法名称来识别调用的内容。
此外,@BEFORE STEP / @AFTER STEP 标签尚不可用,因此您必须为每个步骤定义调用。
https://github.com/cucumber/cucumber-jvm/pull/838#issuecomment-234110573
或者像 junit 或 testng 这样的测试框架可以让你访问执行细节——就像这样:
http://junit.org/junit4/javadoc/4.12/org/junit/rules/TestWatcher.html.
如果您真的只需要步骤名称用于报告目的,您可以简单地解析测试框架生成的 xml 报告。
您可以像这样添加一个步骤
When I log in with the user 'user' and the password 'password'
并在需要登录时重复此步骤
您必须将包含步骤定义的 class 放入每个需要登录的跑步者使用的包中。
我们通过将整个步骤作为参数包装到步骤定义中解决了这个问题。也就是说,步骤
Given I am on my website
翻译成
'Given I am on my website'
并且步骤定义实际上将接受一个与步骤相对应的字符串参数
@And("(.*)") //plus something specific to map step
public void Initialization(String step) throws Exception {
//do something with step
}
我认为 CucumberWithSerenity 注册了一个存储当前步骤名称的监听器。
在您的 Test-Runner 中试试这个:
//import net.serenitybdd.cucumber.CucumberWithSerenity;
@RunWith(CucumberWithSerenity.class)
@CucumberOptions(...
然后在您的步骤中:
//import net.thucydides.core.model.TestStep;
//import net.thucydides.core.steps.StepEventBus;
if (!StepEventBus.getEventBus().isBaseStepListenerRegistered()) {
return "Unknown"; // CucumberWithSerenity is required.
}
String currentStepDescr = StepEventBus.getEventBus().getCurrentStep()
.transform(TestStep::getDescription)
.get();
依赖关系:
<dependency>
<groupId>net.serenity-bdd</groupId>
<artifactId>serenity-core</artifactId>
<version>${serenity.version}</version>
</dependency>
这些钩子会有所帮助:
@BeforeStep
public void beforeStep(Scenario scenario){
System.out.println(scenario.toString());
}
@AfterStep
public void afterStep(Scenario scenario){
System.out.println(scenario.toString());
}
我也有同样的问题。我试图使用 rs79 的答案,但要么我不知道我实际上在用它做什么,要么它不起作用。 Java 给我一个 "AmbiguousStepDefinitionException" 或类似的东西。所以我用了不同的方式。如果你有大量的步骤定义,这需要一些工作,但它有效并且非常简单:
@Then(value = "^The page should navigate to url \"([^\"])\"$", timeout = MAX_TIME)
public void the_page_should_navigate_to_url(String url) {
//below I use a custom class with a static method setStepName() which just sets a string field in the class
CustomClass.setStepName("Then The page should navigate to url " + url);
//Assert
}
现在您无需任何复杂的工具即可访问步骤名称。只需使用 get 方法访问自定义 class 中的步骤变量。希望对您有所帮助。
留在这里以供将来参考...
当前版本的 Cucumber (4.2.5) 具有 BeforeStep 挂钩,但仅提供对当前 运行 场景的访问。
我提取当前步骤所做的是使用反射访问该场景中的步骤;
@BeforeStep
public void beforeStep(Scenario scn) throws Exception {
currentStepIndex++;
Field testCaseField = scn.getClass().getDeclaredField("testCase");
testCaseField.setAccessible(true);
TestCase tc = (TestCase) testCaseField.get(scn);
Field testSteps = tc.getClass().getDeclaredField("testSteps");
testSteps.setAccessible(true);
List<TestStep> teststeps = tc.getTestSteps();
try {
PickleStepTestStep pts = (PickleStepTestStep) teststeps.get(currentStepIndex);
getLog().info("########################");
getLog().info("##########STEP##########");
getLog().info(pts.getStepText());
getLog().info("########################");
currentStepIndex++;
} catch (Exception ignore) {
}
}
唯一的缺点是,您需要 class 级别的 int currentStepIndex,并且每个 @Before
或 @BeforeStep
.
需要加 1
警告使用这种类型的反射可能无法在未来的 Cucumber 版本中工作,因为 Cucumber 团队可以决定更改其内部结构。
我使用@BeforeStep 和@AfterStep 解决了这个问题。它有点老套,所以只有当你知道自己在做什么时才使用它。
public class StepDefBeginEndLogger {
private int currentStepDefIndex = 0;
@BeforeStep
public void doSomethingBeforeStep(Scenario scenario) throws Exception {
Field f = scenario.getClass().getDeclaredField("testCase");
f.setAccessible(true);
TestCase r = (TestCase) f.get(scenario);
//You need to filter out before/after hooks
List<PickleStepTestStep> stepDefs = r.getTestSteps()
.stream()
.filter(x -> x instanceof PickleStepTestStep)
.map(x -> (PickleStepTestStep) x)
.collect(Collectors.toList());
//This object now holds the information about the current step definition
//If you are using pico container
//just store it somewhere in your world state object
//and to make it available in your step definitions.
PickleStepTestStep currentStepDef = stepDefs
.get(currentStepDefIndex);
}
@AfterStep
public void doSomethingAfterStep(Scenario scenario) {
currentStepDefIndex += 1;
}
}
使用自反射获取注释对我来说似乎更直接:
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
@When("^User enters username and password$")
public void userEntersUsernameAndPassword() throws Throwable{
Method callingMethod = new Object() {} .getClass() .getEnclosingMethod();
Annotation myAnnotation = callingMethod.getAnnotations()[0];
System.out.println("myAnnotation=" + myAnnotation);
结果:
myAnnotation=@cucumber.api.java.en.Given(timeout=0, value=^User is in portal page$)
这是处理框架变化的更新。 "testCase" 字段隐藏在 "delegate" 下。我使用 io.cucumber.java 版本 5.7.0
进行了此操作
public String getStepText(io.cucumber.java.Scenario scenario){
String currentStepDescr = null;
//value currentStepDefIndex is tracked in the another class
int currentStepDefIndex = OtherClass.getStepIndex();
Field f = scenario.getClass().getDeclaredField("delegate");
f.setAccessible(true);
TestCaseState tcs = (TestCaseState) f.get(scenario);
Field f2 = tcs.getClass().getDeclaredField("testCase");
f2.setAccessible(true);
TestCase r = (TestCase) f2.get(tcs);
List<PickleStepTestStep> stepDefs = r.getTestSteps()
.stream()
.filter(x -> x instanceof PickleStepTestStep)
.map(x -> (PickleStepTestStep) x)
.collect(Collectors.toList());
PickleStepTestStep currentStepDef = stepDefs
.get(currentStepDefIndex);
currentStepDescr = currentStepDef.getStep().getText();
currentStepDefIndex += 1;
OtherClass.setStepIndex(currentStepDefIndex);
return currentStepDescr ;
}
以下是我的 pom.xml
中的依赖项
<!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-core -->
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-core</artifactId>
<version>5.7.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-testng -->
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-testng</artifactId>
<version>5.7.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-java -->
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>5.7.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-jvm-deps -->
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-jvm-deps</artifactId>
<version>1.0.6</version>
<scope>provided</scope>
</dependency>
作为新手,我无法评论 AndyGee 的答案,但如果您想获得实际名称,则必须使用 .getName() 方法或 .getUri + .getLine() 来获取类似 id 的内容( .getId() 不是 return 唯一 ID)。
@BeforeStep
public void beforeStep(Scenario scenario){
System.out.println(scenario.getName().toString());
}
目前我们正在使用 .getUri() 方法并根据子字符串检查 Uri,以便将来更加灵活。
Scenario: As a user, I want to login to the system
Given I am on my website
When I enter valid credentials
Then I am taken to the home page
可以使用getName()
函数检索场景名称。有没有办法让正在执行的步骤(在 Java
中)?我们预见到在日志记录和报告中使用它。
因此,对于上述情况,在执行相应的步骤定义时将返回 I am on my website
。
您是在询问是否可以获得一些表明步骤 When I enter valid credentials
已执行的日志记录?
如果是,答案是肯定的。
Cucumber 本身没有日志记录的概念,因此您必须添加自己喜欢的日志记录框架。由于 Cucumber 不知道如何通过您最喜欢的日志框架进行日志记录,因此您必须在 Java.
中实现的每个步骤中添加一条日志语句我从未见过自己需要登录。来自 Maven 的执行日志,或者您使用的任何构建工具,已经足够我使用很长时间了。
报告包括已执行的步骤,以便涵盖案例。
作为新手,我不允许发表评论,所以这里有一些信息,假设您使用的是 cucumber-jvm。
简短回答,不,Cucumber 本身没有读取步骤名称的选项。您可以使用方法名称来识别调用的内容。
此外,@BEFORE STEP / @AFTER STEP 标签尚不可用,因此您必须为每个步骤定义调用。
https://github.com/cucumber/cucumber-jvm/pull/838#issuecomment-234110573
或者像 junit 或 testng 这样的测试框架可以让你访问执行细节——就像这样: http://junit.org/junit4/javadoc/4.12/org/junit/rules/TestWatcher.html.
如果您真的只需要步骤名称用于报告目的,您可以简单地解析测试框架生成的 xml 报告。
您可以像这样添加一个步骤
When I log in with the user 'user' and the password 'password'
并在需要登录时重复此步骤
您必须将包含步骤定义的 class 放入每个需要登录的跑步者使用的包中。
我们通过将整个步骤作为参数包装到步骤定义中解决了这个问题。也就是说,步骤
Given I am on my website
翻译成
'Given I am on my website'
并且步骤定义实际上将接受一个与步骤相对应的字符串参数
@And("(.*)") //plus something specific to map step
public void Initialization(String step) throws Exception {
//do something with step
}
我认为 CucumberWithSerenity 注册了一个存储当前步骤名称的监听器。
在您的 Test-Runner 中试试这个:
//import net.serenitybdd.cucumber.CucumberWithSerenity;
@RunWith(CucumberWithSerenity.class)
@CucumberOptions(...
然后在您的步骤中:
//import net.thucydides.core.model.TestStep;
//import net.thucydides.core.steps.StepEventBus;
if (!StepEventBus.getEventBus().isBaseStepListenerRegistered()) {
return "Unknown"; // CucumberWithSerenity is required.
}
String currentStepDescr = StepEventBus.getEventBus().getCurrentStep()
.transform(TestStep::getDescription)
.get();
依赖关系:
<dependency>
<groupId>net.serenity-bdd</groupId>
<artifactId>serenity-core</artifactId>
<version>${serenity.version}</version>
</dependency>
这些钩子会有所帮助:
@BeforeStep
public void beforeStep(Scenario scenario){
System.out.println(scenario.toString());
}
@AfterStep
public void afterStep(Scenario scenario){
System.out.println(scenario.toString());
}
我也有同样的问题。我试图使用 rs79 的答案,但要么我不知道我实际上在用它做什么,要么它不起作用。 Java 给我一个 "AmbiguousStepDefinitionException" 或类似的东西。所以我用了不同的方式。如果你有大量的步骤定义,这需要一些工作,但它有效并且非常简单:
@Then(value = "^The page should navigate to url \"([^\"])\"$", timeout = MAX_TIME)
public void the_page_should_navigate_to_url(String url) {
//below I use a custom class with a static method setStepName() which just sets a string field in the class
CustomClass.setStepName("Then The page should navigate to url " + url);
//Assert
}
现在您无需任何复杂的工具即可访问步骤名称。只需使用 get 方法访问自定义 class 中的步骤变量。希望对您有所帮助。
留在这里以供将来参考...
当前版本的 Cucumber (4.2.5) 具有 BeforeStep 挂钩,但仅提供对当前 运行 场景的访问。
我提取当前步骤所做的是使用反射访问该场景中的步骤;
@BeforeStep
public void beforeStep(Scenario scn) throws Exception {
currentStepIndex++;
Field testCaseField = scn.getClass().getDeclaredField("testCase");
testCaseField.setAccessible(true);
TestCase tc = (TestCase) testCaseField.get(scn);
Field testSteps = tc.getClass().getDeclaredField("testSteps");
testSteps.setAccessible(true);
List<TestStep> teststeps = tc.getTestSteps();
try {
PickleStepTestStep pts = (PickleStepTestStep) teststeps.get(currentStepIndex);
getLog().info("########################");
getLog().info("##########STEP##########");
getLog().info(pts.getStepText());
getLog().info("########################");
currentStepIndex++;
} catch (Exception ignore) {
}
}
唯一的缺点是,您需要 class 级别的 int currentStepIndex,并且每个 @Before
或 @BeforeStep
.
警告使用这种类型的反射可能无法在未来的 Cucumber 版本中工作,因为 Cucumber 团队可以决定更改其内部结构。
我使用@BeforeStep 和@AfterStep 解决了这个问题。它有点老套,所以只有当你知道自己在做什么时才使用它。
public class StepDefBeginEndLogger {
private int currentStepDefIndex = 0;
@BeforeStep
public void doSomethingBeforeStep(Scenario scenario) throws Exception {
Field f = scenario.getClass().getDeclaredField("testCase");
f.setAccessible(true);
TestCase r = (TestCase) f.get(scenario);
//You need to filter out before/after hooks
List<PickleStepTestStep> stepDefs = r.getTestSteps()
.stream()
.filter(x -> x instanceof PickleStepTestStep)
.map(x -> (PickleStepTestStep) x)
.collect(Collectors.toList());
//This object now holds the information about the current step definition
//If you are using pico container
//just store it somewhere in your world state object
//and to make it available in your step definitions.
PickleStepTestStep currentStepDef = stepDefs
.get(currentStepDefIndex);
}
@AfterStep
public void doSomethingAfterStep(Scenario scenario) {
currentStepDefIndex += 1;
}
}
使用自反射获取注释对我来说似乎更直接:
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
@When("^User enters username and password$")
public void userEntersUsernameAndPassword() throws Throwable{
Method callingMethod = new Object() {} .getClass() .getEnclosingMethod();
Annotation myAnnotation = callingMethod.getAnnotations()[0];
System.out.println("myAnnotation=" + myAnnotation);
结果:
myAnnotation=@cucumber.api.java.en.Given(timeout=0, value=^User is in portal page$)
这是处理框架变化的更新。 "testCase" 字段隐藏在 "delegate" 下。我使用 io.cucumber.java 版本 5.7.0
进行了此操作public String getStepText(io.cucumber.java.Scenario scenario){
String currentStepDescr = null;
//value currentStepDefIndex is tracked in the another class
int currentStepDefIndex = OtherClass.getStepIndex();
Field f = scenario.getClass().getDeclaredField("delegate");
f.setAccessible(true);
TestCaseState tcs = (TestCaseState) f.get(scenario);
Field f2 = tcs.getClass().getDeclaredField("testCase");
f2.setAccessible(true);
TestCase r = (TestCase) f2.get(tcs);
List<PickleStepTestStep> stepDefs = r.getTestSteps()
.stream()
.filter(x -> x instanceof PickleStepTestStep)
.map(x -> (PickleStepTestStep) x)
.collect(Collectors.toList());
PickleStepTestStep currentStepDef = stepDefs
.get(currentStepDefIndex);
currentStepDescr = currentStepDef.getStep().getText();
currentStepDefIndex += 1;
OtherClass.setStepIndex(currentStepDefIndex);
return currentStepDescr ;
}
以下是我的 pom.xml
中的依赖项<!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-core -->
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-core</artifactId>
<version>5.7.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-testng -->
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-testng</artifactId>
<version>5.7.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-java -->
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java</artifactId>
<version>5.7.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.cucumber/cucumber-jvm-deps -->
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-jvm-deps</artifactId>
<version>1.0.6</version>
<scope>provided</scope>
</dependency>
作为新手,我无法评论 AndyGee 的答案,但如果您想获得实际名称,则必须使用 .getName() 方法或 .getUri + .getLine() 来获取类似 id 的内容( .getId() 不是 return 唯一 ID)。
@BeforeStep
public void beforeStep(Scenario scenario){
System.out.println(scenario.getName().toString());
}
目前我们正在使用 .getUri() 方法并根据子字符串检查 Uri,以便将来更加灵活。