Cucumber和Hooks

2023/05/09

1. 简介

当我们想要为每个场景或步骤执行特定操作时,Cucumber的钩子可以派上用场,并且这些钩子不是定义在Feature文件代码中。

在本教程中,我们将介绍Cucumber中的@Before、@BeforeStep、@AfterStep和@After钩子。

2. Cucumber中的钩子概述

2.1 什么时候应该使用钩子?

钩子可用于执行不属于业务功能的后台任务。此类任务可能是:

  • 启动浏览器
  • 设置或清除cookie
  • 连接到数据库
  • 检查系统状态
  • 监控

监控的一个用例是使用实时测试进度更新仪表板。

钩子在Gherkin代码中是不可见的。因此,我们不应该将它们视为Cucumber Background或Given步骤的替代品

我们将演示一个示例,其中我们在测试执行期间使用钩子截取屏幕截图。

2.2 钩子范围

钩子会影响每个场景。因此,最好在专用配置类中定义所有钩子。

没有必要在每个步骤定义代码类中定义相同的钩子。如果我们将步骤定义代码与钩子定义在同一个类中,则代码的可读性就会降低。

3. 钩子

让我们先看一下各个钩子。然后,我们将查看一个完整的示例,其中我们将看到钩子在组合时如何执行。

3.1 @Before

使用@Before标注的方法将在每个场景之前执行,注意该注解来自Cucumber库而不是JUnit。在我们的示例中,我们将在每个场景之前启动浏览器:

@Before
public void initialization() {
    startBrowser();
}

如果我们使用@Before标注多个方法,我们可以显式定义步骤的执行顺序

@Before(order = 2)
public void beforeScenario() {
    takeScreenshot();
}

上面的方法在initialization方法之后执行,因为我们将2作为order参数的值传递给注解。我们还可以将1作为initialization方法的order参数的值:

@Before(order = 1)
public void initialization()

因此,当我们执行一个场景时,initialization()首先执行,然后执行beforeScenario()。

3.2 @BeforeStep

使用@BeforeStep标注的方法在每个步骤之前执行。假设我们想在每个步骤执行之前截图,我们可以使用@BeforeStep标注beforeStep()方法:

@BeforeStep
public void beforeStep() {
    takeScreenshot();
}

3.3 @AfterStep

使用@AfterStep标注的方法在每个步骤之后执行

@AfterStep
public void afterStep() {
    takeScreenshot();
}

我们在这里使用@AfterStep在每一步之后截取屏幕截图。无论步骤是成功完成还是失败,都会执行这个钩子

3.4 @After

使用@After注解的方法在每个场景之后执行

@After
public void afterScenario() {
    takeScreenshot();
    closeBrowser();
}

在我们的示例中,我们将截取最终屏幕截图并关闭浏览器。无论场景是否成功完成,都会执行这个钩子

3.5 Scenario参数

使用钩子注解标注的方法可以接收Scenario类型的参数:

@After
public void beforeScenario(Scenario scenario) { 
    // some code
}

Scenario类型的对象包含有关当前场景的信息。包括场景名称、步骤数、步骤名称和状态(通过或失败)。如果我们想对通过和失败的测试执行不同的操作,这会很有用

4. 钩子执行

4.1 快乐流

现在让我们看看当我们使用所有四种类型的钩子运行Cucumber场景时会发生什么:

Feature: Book Store With Hooks

    Background: The Book Store
        Given The following books are available in the store
            | The Devil in the White City          | Erik Larson |
            | The Lion, the Witch and the Wardrobe | C.S. Lewis  |
            | In the Garden of Beasts              | Erik Larson |

    Scenario: 1 - Find books by author
        When I ask for a book by the author Erik Larson
        Then The salesperson says that there are 2 books

    Scenario: 2 - Find books by author, but isn't there
        When I ask for a book by the author Marcel Proust
        Then The salesperson says that there are 0 books

查看IntelliJ IDEA中的测试运行结果,我们可以看到执行顺序:

首先,我们的两个@Before钩子执行。然后在每个步骤之前和之后,@BeforeStep和@AfterStep钩子分别运行。最后@After钩子运行。所有钩子都针对这两个场景执行。

4.2 不快乐的流程:某个步骤失败

让我们看看如果某个步骤失败会发生什么。正如我们在下面的截图中看到的,失败步骤的@Before和@After钩子都被执行。跳过后续步骤,最后@After钩子执行:

@After的行为类似于Java中try-catch之后的finally子句。如果步骤失败,我们可以使用它来执行清理任务。在我们的示例中,即使场景失败,我们仍然会执行beforeScenario()方法。

4.3 不快乐的流程:某个钩子失败

让我们看看当钩子本身失败时会发生什么。在下面的示例中,第一个@BeforeStep失败。

在这种情况下,实际步骤不会运行,但@AfterStep钩子会运行。后续步骤也不会运行,而@After钩子在最后执行:

5. 带标签的条件执行

钩子是全局定义的,会影响所有场景和步骤。但是,在Cucumber标签的帮助下,我们可以准确定义应该针对哪些场景执行钩子:

@Before(order = 2, value = "@Screenshots")
public void beforeScenario() {
    takeScreenshot();
}

通过指定value属性为“@Screenshots”,此钩子将仅针对标记为@Screenshots的场景执行:

@Screenshots
Scenario: 1 - Find books by author 
    When I ask for a book by the author Erik Larson 
    Then The salesperson says that there are 2 books

6. Java 8

我们可以添加Cucumber Java 8支持来将所有钩子以Lambda表达式的形式定义。

回顾上面示例中的初始化钩子:

@Before(order = 2)
public void initialization() {
    startBrowser();
}

通过使用Lambda表达式我们可以重写为:

public BookStoreWithHooksRunSteps() {
    Before(2, () -> startBrowser());
}

这同样适用于@BeforeStep、@After和@AfterStep。

7. 总结

在本文中,我们了解了如何定义Cucumber钩子。

我们讨论了在哪些情况下应该使用它们,什么时候不应该使用它们。然后,我们看到了钩子执行的顺序以及我们如何实现条件执行。

最后,我们介绍了如何使用Java 8 Lambda表达式定义钩子。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章