在 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,以便将来更加灵活。