2023-09-01

让我们从 Laravel、BDD 和你开始


让我们从 Laravel、BDD 和你开始

欢迎来到有关使用行为驱动开发 (BDD) 方法开发 Laravel 应用程序的系列。全栈 BDD 看起来很复杂且令人生畏。有多少开发人员就有多少种实现此目的的方法。

在本系列中,我将引导您了解使用 Behat 和 PhpSpec 从头开始​​设计 Laravel 应用程序的方法。

一般来说,关于 BDD 的资源有很多,但 Laravel 的具体材料很难找到。因此,在本系列中,我们将更多地关注 Laravel 相关方面,而不是您可以在许多其他地方阅读的一般内容。

描述行为

在描述行为(也称为编写故事和规范)时,我们将使用由外而内的方法。这意味着每次我们构建一个新功能时,我们都会从编写整个用户故事开始。这通常是从客户或利益相关者的角度来看的。

当我们这样做时,我们期望发生什么? 块引用>

除非我们重构现有代码,否则除非我们遇到失败的红色步骤,否则我们不允许编写任何代码。

有时,有必要迭代地解决我们正在处理的故事或功能(我交替使用的两个词)的一小部分。这通常意味着使用 PhpSpec 编写规范。有时,在整个故事(在接受级别上)通过之前,需要在集成或单元级别上进行多次迭代。这一切听起来很复杂,但事实并非如此。我坚信边做边学,所以我认为一旦我们开始编写一些实际的代码,一切都会变得更有意义。

我们将在四个不同的层面上编写故事和规范:

1. 验收

大多数时候,我们的功能套件将充当我们的接受层。我们在功能套件中描述功能的方式将与我们编写接受故事的方式(使用自动化浏览器框架)非常相似,因此会产生大量重复。

只要故事从客户的角度描述了行为,它们就可以作为接受故事。我们将使用 Symfony DomCrawler 来测试应用程序的输出。在本系列的后面,我们可能会发现我们需要通过也可以运行 JavaScript 的实际浏览器进行测试。通过浏览器进行测试增加了一些新的问题,因为我们需要确保在套件运行时加载小时测试环境。

2. 功能性

在我们的功能套件中,我们将可以访问Laravel应用程序,这非常方便。首先,它可以轻松区分环境。其次,不通过浏览器使我们的测试套件更快。每当我们想要实现一个新功能时,我们都会使用 Behat 在我们的功能套件中编写一个故事。

3. 集成

我们的集成套件将测试应用程序核心部分的行为,这些部分不一定需要访问 Laravel。集成套件通常是 Behat 故事和 PhpSpec 规范的混合体。

4.单位

我们的单元测试将用 PhpSpec 编写,并将测试应用程序核心的独立小单元。我们的实体、值对象等都会有规格。

案例

在本系列中,从下一篇文章开始,我们将构建一个跟踪时间的系统。我们将首先通过编写 Behat 特征来描述外部行为。我们的应用程序的内部行为将使用 PhpSpec 进行描述。

这两个工具一起将帮助我们对正在构建的应用程序的质量感到满意。我们将主要在三个层面上编写功能和规格:

  1. 功能性
  2. 集成
  3. 单位

在我们的功能套件中,我们将以无头模式抓取应用程序的 HTTP 响应,这意味着我们不会通过浏览器。这将使与 Laravel 交互变得更容易,并使我们的功能套件也充当我们的接受层。

稍后,如果我们最终拥有更复杂的 UI 并且可能还需要测试一些 JavaScript,我们可能会添加专用的验收套件。该系列仍在进行中,因此请随时在评论部分提出您的建议。

我们的设置

请注意,在本教程中,我假设您全新安装并运行了 Laravel (4.2)。最好您也使用 Laravel Homestead,这就是我在编写此代码时使用的。

在开始任何实际工作之前,让我们确保 Behat 和 PhpSpec 已启动并运行。首先,每当我开始一个新的 Laravel 项目时,我喜欢做一些清理工作并删除我不需要的东西:

git rm -r app/tests/ phpunit.xml CONTRIBUTING.md
登录后复制

如果您删除这些文件,请务必相应地更新您的 composer.json 文件:

"autoload": {
    "classmap": [
        "app/commands",
        "app/controllers",
        "app/models",
        "app/database/migrations",
        "app/database/seeds"
    ]
},
登录后复制

当然:

$ composer dump-autoload
登录后复制

现在我们准备好引入我们需要的 BDD 工具了。只需将 require-dev 部分添加到您的 composer.json

"require": {
    "laravel/framework": "4.2.*"
},
"require-dev": {
    "behat/behat": "~3.0",
    "phpspec/phpspec": "~2.0",
    "phpunit/phpunit": "~4.1"
},
登录后复制

“我们为什么要引入 PHPUnit?”你可能在想?在本系列中,我们不会编写优秀的 PHPUnit 测试用例,但断言与 Behat 一起是一个方便的工具。我们将在本文后面编写第一个功能时看到这一点。

请记住在修改 composer.json 后更新依赖项:

$ composer update --dev
登录后复制

我们即将完成安装和设置。 PhpSpec 开箱即用:

$ vendor/bin/phpspec run

0 specs
0 examples 
0ms
登录后复制

但是 Behat 需要使用 --init 选项快速运行才能设置所有内容:

$ vendor/bin/behat --init

+d features - place your *.feature files here
+d features/bootstrap - place your context classes here
+f features/bootstrap/FeatureContext.php - place your definitions, transformations and hooks here

$ vendor/bin/behat

No scenarios
No steps
0m0.14s (12.18Mb)
登录后复制

第一个命令创建了一个闪亮的新 FeatureContext 类,我们可以在其中编写功能所需的步骤定义:

<?php

use Behat/Behat/Context/SnippetAcceptingContext;
use Behat/Gherkin/Node/PyStringNode;
use Behat/Gherkin/Node/TableNode;

/**
 * Behat context class.
 */
class FeatureContext implements SnippetAcceptingContext
{
    /**
     * Initializes context.
     *
     * Every scenario gets its own context object.
     * You can also pass arbitrary arguments to the context constructor through behat.yml.
     */
    public function __construct()
    {
    }
}
登录后复制

编写我们的第一个功能

我们的第一个功能非常简单:我们只需确保新的 Laravel 安装以“您已到达”来迎接我们。在主页上。我还添加了一个相当愚蠢的 Given 步骤,假设我登录了 ,这仅用于显示在我们的功能中与 Laravel 交互是多么容易。

从技术上讲,我将这种类型的功能归类为功能测试,因为它与框架交互,但它也可以作为验收测试,因为我们不会看到通过浏览器测试运行类似测试有任何不同的结果工具。现在我们将坚持使用我们的功能测试套件。

继续创建一个 welcome.feature 文件并将其放入 features/functions:

# features/functional/welcome.feature

Feature: Welcoming developer
    As a Laravel developer
    In order to proberly begin a new project
    I need to be greeted upon arrival

    Scenario: Greeting developer on homepage
        Given I am logged in
        When I visit "/"
        Then I should see "You have arrived."
登录后复制

通过将功能特性放在 featured 目录中,我们以后可以更轻松地管理我们的套件。我们不希望不需要 Laravel 的集成类型功能必须等待缓慢的功能套件。

我喜欢让事情保持美观和干净,所以我相信我们应该为我们的功能套件提供一个专用的功能上下文,以便我们可以访问 Laravel。您可以继续复制现有的 FeatureContext 文件并将类名称更改为 LaravelFeatureContext。为此,我们还需要一个 behat.yml 配置文件。

在项目的根目录中创建一个并添加以下内容:

default:
    suites:
        functional:
            paths: [ %paths.base%/features/functional ]
            contexts: [ LaravelFeatureContext ]
登录后复制

我认为这里的 YAML 是非常不言自明的。我们的功能套件将在 functioning 目录中查找功能,并通过 LaravelFeatureContext 运行它们。

如果我们此时尝试运行 Behat,它会告诉我们实现必要的步骤定义。我们可以使用以下命令让 Behat 将空脚手架方法添加到 LaravelFeatureContext 中:

$ vendor/bin/behat --dry-run --append-snippets
$ vendor/bin/behat

Feature: Welcoming developer
    As a Laravel developer
    In order to proberly begin a new project
    I need to be greeted upon arival

  Scenario: Greeting developer on homepage # features/functional/welcome.feature:6
    Given I am logged in                   # LaravelFeatureContext::iAmLoggedIn()
      TODO: write pending definition
    When I visit "/"                       # LaravelFeatureContext::iVisit()
    Then I should see "You have arrived."  # LaravelFeatureContext::iShouldSee()

1 scenario (1 pending)
3 steps (1 pending, 2 skipped)
0m0.28s (12.53Mb)
登录后复制

现在,正如您从输出中看到的那样,我们已准备好开始实施第一个步骤:鉴于我已登录

Laravel 附带的 PHPUnit 测试用例允许我们执行诸如 $this->be($user) 之类的操作,它会登录给定用户。最终,我们希望能够像使用 PHPUnit 一样与 Laravel 交互,所以让我们继续编写“我们希望拥有”的步骤定义代码:

/**
 * @Given I am logged in
 */
public function iAmLoggedIn()
{
    $user = new User;

    $this->be($user);
}
登录后复制

这当然行不通,因为 Behat 不知道 Laravel 的具体内容,但我很快就会向您展示让 Behat 和 Laravel 很好地协同工作是多么容易。

如果您查看 Laravel 源代码并找到 Illuminate/Foundation/Testing/TestCase 类(默认测试用例扩展自该类),您会发现从 Laravel 4.2 开始,一切都已更改转移到一个特质。 ApplicationTrait 现在负责启动 Application 实例、设置 HTTP 客户端并为我们提供一些辅助方法,例如 be()

这非常酷,主要是因为这意味着我们可以将其拉入我们的 Behat 上下文中,几乎不需要任何设置。我们还可以访问 AssertionsTrait,但这仍然与 PHPUnit 相关。

当我们引入特征时,我们需要做两件事。我们需要一个 setUp() 方法,如 Illuminate/Foundation/Testing/TestCase 类中的方法,并且需要一个 createApplication() 方法,如默认 Laravel 测试用例中的方法。其实我们直接复制这两个方法就可以了。

只有一件事需要注意:在 PHPUnit 中,方法 setUp() 会在每次测试之前自动调用。为了在 Behat 中实现相同的效果,我们可以使用 @BeforeScenario 注释。

将以下内容添加到您的 LaravelFeatureContext

use Illuminate/Foundation/Testing/ApplicationTrait;

/**
 * Behat context class.
 */
class LaravelFeatureContext implements SnippetAcceptingContext
{
    /**
     * Responsible for providing a Laravel app instance.
     */
    use ApplicationTrait;

    /**
     * Initializes context.
     *
     * Every scenario gets its own context object.
     * You can also pass arbitrary arguments to the context constructor through behat.yml.
     */
    public function __construct()
    {
    }

    /**
     * @BeforeScenario
     */
    public function setUp()
    {
        if ( ! $this->app)
        {
            $this->refreshApplication();
        }
    }

    /**
     * Creates the application.
     *
     * @return /Symfony/Component/HttpKernel/HttpKernelInterface
     */
    public function createApplication()
    {
        $unitTesting = true;

        $testEnvironment = 'testing';

        return require __DIR__.'/../../bootstrap/start.php';
    }
登录后复制

非常简单,看看我们运行 Behat 时得到的结果:

$ vendor/bin/behat

Feature: Welcoming developer
    As a Laravel developer
    In order to proberly begin a new project
    I need to be greeted upon arival

  Scenario: Greeting developer on homepage # features/functional/welcome.feature:6
    Given I am logged in                   # LaravelFeatureContext::iAmLoggedIn()
    When I visit "/"                       # LaravelFeatureContext::iVisit()
      TODO: write pending definition
    Then I should see "You have arrived."  # LaravelFeatureContext::iShouldSee()

1 scenario (1 pending)
3 steps (1 passed, 1 pending, 1 skipped)
0m0.73s (17.92Mb)
登录后复制

绿色的第一步,这意味着我们的设置正在运行!

接下来,我们可以实现 当我访问 步骤。这个非常简单,我们可以简单地使用 ApplicationTrait 提供的 call() 方法。一行代码即可实现:

/**
 * @When I visit :uri
 */
public function iVisit($uri)
{
    $this->call('GET', $uri);
}
登录后复制

最后一步,然后我应该看到,需要更多一点,我们需要引入两个依赖项。我们需要 PHPUnit 来进行断言,并且需要 Symfony DomCrawler 来搜索“您已到达”。文本。

我们可以这样实现:

use PHPUnit_Framework_Assert as PHPUnit;
use Symfony/Component/DomCrawler/Crawler;

...

/**
 * @Then I should see :text
 */
public function iShouldSee($text)
{
    $crawler = new Crawler($this->client->getResponse()->getContent());

    PHPUnit::assertCount(1, $crawler->filterXpath("//text()[. = '{$text}']"));
}
登录后复制

这与您使用 PHPUnit 时编写的代码几乎相同。 filterXpath() 部分有点令人困惑,我们现在不用担心它,因为它有点超出了本文的范围。请相信我,它有效。

最后一次跑步是个好消息:

$ vendor/bin/behat
Feature: Welcoming developer
    As a Laravel developer
    In order to proberly begin a new project
    I need to be greeted upon arival

  Scenario: Greeting developer on homepage # features/functional/welcome.feature:6
    Given I am logged in                   # LaravelFeatureContext::iAmLoggedIn()
    When I visit "/"                       # LaravelFeatureContext::iVisit()
    Then I should see "You have arrived."  # LaravelFeatureContext::iShouldSee()

1 scenario (1 passed)
3 steps (3 passed)
0m0.82s (19.46Mb)
登录后复制

该功能正在按预期工作,并且开发人员在抵达时受到欢迎。

结论

完整的 LaravelFeatureContext 现在应该与此类似:

<?php

use Behat/Behat/Context/SnippetAcceptingContext;
use Behat/Gherkin/Node/PyStringNode;
use Behat/Gherkin/Node/TableNode;

use PHPUnit_Framework_Assert as PHPUnit;
use Symfony/Component/DomCrawler/Crawler;

use Illuminate/Foundation/Testing/ApplicationTrait;

/**
 * Behat context class.
 */
class LaravelFeatureContext implements SnippetAcceptingContext
{
    /**
     * Responsible for providing a Laravel app instance.
     */
    use ApplicationTrait;

    /**
     * Initializes context.
     *
     * Every scenario gets its own context object.
     * You can also pass arbitrary arguments to the context constructor through behat.yml.
     */
    public function __construct()
    {
    }

    /**
     * @BeforeScenario
     */
    public function setUp()
    {
        if ( ! $this->app)
        {
            $this->refreshApplication();
        }
    }

    /**
     * Creates the application.
     *
     * @return /Symfony/Component/HttpKernel/HttpKernelInterface
     */
    public function createApplication()
    {
        $unitTesting = true;

        $testEnvironment = 'testing';

        return require __DIR__.'/../../bootstrap/start.php';
    }

    /**
     * @Given I am logged in
     */
    public function iAmLoggedIn()
    {
        $user = new User;

        $this->be($user);
    }

    /**
     * @When I visit :uri
     */
    public function iVisit($uri)
    {
        $this->call('GET', $uri);
    }

    /**
     * @Then I should see :text
     */
    public function iShouldSee($text)
    {
        $crawler = new Crawler($this->client->getResponse()->getContent());

        PHPUnit::assertCount(1, $crawler->filterXpath("//text()[. = '{$text}']"));
    }
}
登录后复制

随着我们继续使用 BDD 开发新的 Laravel 应用程序,我们现在已经有了一个非常好的基础。我希望我已经向您证明了让 Laravel 和 Behat 很好地协同工作是多么容易。

我们在第一篇文章中讨论了许多不同的主题。不用担心,随着本系列的继续,我们将更深入地研究一切。如果您有任何问题或建议,请留言。

以上就是让我们从 Laravel、BDD 和你开始的详细内容,更多请关注php中文网其它相关文章!

https://www.php.cn/faq/600857.html

发表回复

Your email address will not be published. Required fields are marked *