首页 > 解决方案 > 当有多个场景时,Behat 挂起,但适用于单个场景

问题描述

我有这样写的 Behat 测试用例:

Feature: Checkout
In order to buy products
As a customer
I need to be able to checkout items in the cart

Background: 
    Given step 1
    And step 2

@Ready
Scenario: Deliver now
    When step 3
    Then step 4

@NoneReady
Scenario: Deliver later
    When step a
    Then step b
    And step c


@AddressNotCovered
Scenario: Address Not Covered
    When step i
    Then step ii

如果我在单个标签上运行 behat,它就可以正常工作:

$ behat --tags=Ready
Feature: Checkout
  In order to buy products
  As a customer
  I need to be able to checkout items in the cart

  @Ready
  Scenario: Deliver now                                                                 # tests/features/Checkout/CheckOut.feature:9
    step 1
    And step 2 
    .. 

1 scenario (1 passed)
7 steps (7 passed)
0m3.85s (36.62Mb)

但是如果我在多个标签上运行它,它会挂在第一个标签的末尾:

behat --tags=Ready,AddressNotCovered
Feature: Checkout
  In order to buy products
  As a customer
  I need to be able to checkout items in the cart

  @Ready
  Scenario: Deliver now                                                                 # tests/features/Checkout/CheckOut.feature:9
    Given step ..
    ..
    And .. 

    // hangs here

我究竟做错了什么?

环境

Laravel 5.4
Behat 3.1.0
PHP 7.1.23
PHPUnit 5.7.27

来自我的 composer.json

"require": {
    "php": ">=5.5.9",
    "laravel/framework": "5.4.*",
    ..
    "behat/behat": "3.1.0",
    "laracasts/behat-laravel-extension": "^1.1",
},
"require-dev": {
    "phpunit/phpunit": "~5.7",
    "phpspec/phpspec": "~2.1",
    "johnkary/phpunit-speedtrap": "^1.0",
},

Behat.yml

default:
  extensions:
      Laracasts\Behat:
          env_path: .env.testing
  autoload:
    - ./tests/features/bootstrap
  suites:

    Checkout:
      paths: [./tests/features/Checkout]
      contexts: [CheckoutFeatureContext]

更新

我试图创建示例小黄瓜来说明上述问题。我在尝试自动附加片段时遇到了同样的问题。附加片段适用于单个场景,但在多个场景中失败:

工作示例:单一场景

# tests/features/Example/Example.feature

Feature: Example
In order to show dev team how to use behat/gherkin using background 
As a developer
I need to be able write gherkin using a background and multiple scenarios 
And all scenarios should run 

Background: 
    Givens setup condition 1 
    And setup condition 2 


Scenario: scenario one 
    When I perform first sample trigger point 
    Then result one must happen 
    And result two must happen 

当我运行以下命令时

behat tests/features/Example/Example.feature  --append-snippets

添加片段效果很好

Feature: Example
  In order to show dev team how to use behat/gherkin using background
  As a developer
  I need to be able write gherkin using a background and multiple scenarios
  And all scenarios should run

  Background:             # tests/features/Example/Example.feature:9
      Givens setup condition 1
    And setup condition 2

  Scenario: scenario one                      # tests/features/Example/Example.feature:13
    When I perform first sample trigger point
    Then result one must happen
    And result two must happen

1 scenario (1 undefined)
4 steps (4 undefined)
0m0.48s (24.63Mb)

u tests/features/bootstrap/FeatureContext.php - `setup condition 2` definition added
u tests/features/bootstrap/FeatureContext.php - `I perform first sample trigger point` definition added
u tests/features/bootstrap/FeatureContext.php - `result one must happen` definition added
u tests/features/bootstrap/FeatureContext.php - `result two must happen` definition added

失败示例:多个场景

当我们有多个场景时

# tests/features/Example/Example.feature

Feature: Example
In order to show dev team how to use behat/gherkin using background 
As a developer
I need to be able write gherkin using a background and multiple scenarios 
And all scenarios should run 

Background: 
    Givens setup condition 1 
    And setup condition 2 

Scenario: scenario one 
    When I perform first sample trigger point 
    Then result one must happen 
    And result two must happen 

Scenario: scenario two 
    When I perform second sample trigger point 
    Then result a must happen 
    And result b must happen 

运行相同的 --append-snippets 命令阻塞:

Feature: Example
  In order to show dev team how to use behat/gherkin using background
  As a developer
  I need to be able write gherkin using a background and multiple scenarios
  And all scenarios should run

  Background:             # tests/features/Example/Example.feature:9
      Givens setup condition 1
    And setup condition 2

  Scenario: scenario one                      # tests/features/Example/Example.feature:13
    When I perform first sample trigger point
    Then result one must happen
    And result two must happen

^C // had to abort here

标签: phplaravel-5laravel-5.4bddbehat

解决方案


事实证明,上面的例子太简单了。在做了一些研究(这篇文章特别有帮助)之后,我意识到这种“停滞”是由于每次测试后都拆除了数据库。所以这就是修复它的原因:

首先,我在 FeatureContext 类中替换DatabaseTransactions为:DatabaseMigrations

class FeatureContext extends TestCase implements  Context, SnippetAcceptingContext
{

    use DatabaseMigrations, ..

鉴于上述情况,我从我的 bitbucket 管道脚本中删除了手动迁移命令

- php artisan --env=testing config:cache

这是有道理的,因为使用新代码,每次测试之前数据库总是会被刷新和迁移。

然后我将setUp()调用添加到behat hooks

/** @BeforeScenario */
public function before(BeforeScenarioScope $scope)
{
    parent::setUp();
}

就是这样。这个解决方案最好的部分是它完全使我的本地测试环境与 bitbucket 管道的环境保持一致,因此结果总是相同的。

进一步解释:来自我们的 wiki

一般来说,最好是重新开始每个测试,不要留下前一个测试的剩余部分(尤其是在涉及数据库时)。用 laravel 的话来说

在每次测试后重置数据库通常很有用,这样前一次测试的数据不会干扰后续测试。

为此,我们使用迁移。话虽如此,由于我们实际上使用的是 Behat,因此我们需要在每个场景生命周期之前和之后进行这种迁移。我们使用Behat 的 hooks来做到这一点。我们在这里这样做:

/** @BeforeScenario */
    public function before(BeforeScenarioScope $scope)
    {
        parent::setUp();
    }

parent::setUP()告诉Laravel 框架在每个场景之前和之后做必要的工作:

 protected function setUp()
    {
        if (! $this->app) {
            $this->refreshApplication();
        }
        $this->setUpTraits(); <---- here
        ..

这反过来又调用了设置特征:

   protected function setUpTraits()
    {
        $uses = array_flip(class_uses_recursive(static::class));
        if (isset($uses[DatabaseMigrations::class])) {
            $this->runDatabaseMigrations();
        }
        ..

这叫这个

public function runDatabaseMigrations()
{
    $this->artisan('migrate:fresh');
    $this->app[Kernel::class]->setArtisan(null);
    $this->beforeApplicationDestroyed(function () {
        $this->artisan('migrate:rollback');
        RefreshDatabaseState::$migrated = false;
    });
}

请注意,一旦应用程序被销毁,Laravel 也会回滚更改。理解这一点非常重要,这样可以防止 Behat 在有多个场景并且在它们之前出现给定的情况下停滞不前。还要记住,当我们像这样使用 Gherkin 时:

Feature: Checkout
In order to buy products
As a customer
I need to be able to checkout items in the cart

Background: 
    Given step 1
    And step 2

@Ready
Scenario: Deliver now
    When step 3
    Then step 4

@NoneReady
Scenario: Deliver later
    When step a
    Then step b
    And step c

然后每个场景都从后台步骤开始,而不是在场景步骤本身

例子:

Feature: Checkout
In order to buy products
As a customer
I need to be able to checkout items in the cart

Background:
    Given step 1  <-- every scenario starts here, so we call setup before this step
    And step 2

@Ready
Scenario: Deliver now
    When step 3 <-- not here
    Then step 4

@NoneReady
Scenario: Deliver later
    When step a
    Then step b
    And step c

推荐阅读