ZetCode

PHP Behat 库

最后修改于 2025 年 3 月 19 日

Behat 是一个用于 PHP 的行为驱动开发 (BDD) 框架。它允许您编写可读的测试来定义应用程序行为。本指南涵盖了设置、特性编写以及带有实际示例的测试执行。

安装

使用 Composer 在您的项目目录中安装 Behat 和用于小数的 Brick Math

composer require --dev behat/behat brick/math

这会将 Behat 和 Brick Math 添加为您的 PHP 项目的开发依赖项。

初始化 Behat

通过运行此命令在您的项目中初始化 Behat

vendor/bin/behat --init

这会为 Behat 设置目录结构和配置文件。

编写特性

Behat 使用 Gherkin 语法来实现可读的行为规范。在 features 目录中创建特性文件,用于实际的电子商务示例。

features/cart.feature
Feature: Shopping Cart
  As a customer
  To manage my purchases
  I want to add items to my cart and see the total

  Scenario: Add item to cart
    Given I have an empty cart
    When I add a "Laptop" costing 999.99
    Then the cart total should be 999.99

  Scenario: Add multiple items to cart
    Given I have an empty cart
    When I add a "Mouse" costing 29.99
    And I add a "Keyboard" costing 59.99
    Then the cart total should be 89.98

此特性测试电子商务应用程序的购物车功能。它定义了两个场景:添加单个项目和添加多个项目。

第一个场景确保单个项目(“笔记本电脑”)正确更新总额为 999.99。第二个测试添加“鼠标”和“键盘”,验证总额为 89.98,模拟真实的购物行为。

features/login.feature
Feature: User Login
  As a registered user
  To access my account
  I want to log in with my credentials

  Scenario: Successful login
    Given I am on the login page
    When I enter username "user1" and password "pass123"
    Then I should be logged in successfully

此特性测试用户身份验证,这是大多数 Web 应用程序的关键部分。它侧重于具有预定义凭据的成功登录场景。

它首先将用户置于登录页面,然后模拟输入用户名和密码。最后一步检查登录是否成功,模拟安全系统中常见的用户流程。

features/order.feature
Feature: Order Processing
  As a customer
  To complete my purchase
  I want to process my cart into an order

  Scenario: Process cart to order
    Given I have an empty cart
    And I add a "Book" costing 19.99
    When I process the order
    Then the order total should be 19.99

此特性测试电子商务应用程序中的订单创建流程。它验证购物车中的项目是否可以转换为最终订单。

该场景从一个空购物车开始,添加一个价格为 19.99 的“书籍”,然后将其处理成一个订单。它检查订单总额是否与购物车总额匹配,确保交易正确完成。

这些特性测试购物车管理、用户登录和订单处理。

定义步骤定义

步骤定义是将 Gherkin 步骤链接到代码的 PHP 方法。使用严格类型和十进制数编辑 features/bootstrap 中的上下文类。

features/bootstrap/FeatureContext.php
<?php

declare(strict_types=1);

use Behat\Behat\Context\Context;
use Brick\Math\BigDecimal;

class FeatureContext implements Context
{
    private array $cart = [];
    private BigDecimal $total;
    private bool $isLoggedIn = false;
    private string $currentPage = '';
    private ?BigDecimal $orderTotal = null;

    /** @Given I have an empty cart */
    public function iHaveAnEmptyCart(): void
    {
        $this->cart = [];
        $this->total = BigDecimal::zero();
    }

    /** @When I add a :item costing :price */
    public function iAddItemCosting(string $item, string $price): void
    {
        $cost = BigDecimal::of($price);
        $this->cart[] = ['item' => $item, 'price' => $cost];
        $this->total = $this->total->plus($cost);
    }

    /** @Then the cart total should be :expected */
    public function theCartTotalShouldBe(string $expected): void
    {
        $expectedTotal = BigDecimal::of($expected);
        if (!$this->total->isEqualTo($expectedTotal)) {
            throw new Exception("Expected $expectedTotal, got $this->total");
        }
    }

    /** @Given I am on the login page */
    public function iAmOnTheLoginPage(): void
    {
        $this->currentPage = 'login';
        $this->isLoggedIn = false;
    }

    /** @When I enter username :username and password :password */
    public function iEnterUsernameAndPassword(
        string $username,
        string $password
    ): void {
        if ($this->currentPage !== 'login') {
            throw new Exception('Not on login page');
        }
        $this->isLoggedIn = $username === 'user1' && $password === 'pass123';
    }

    /** @Then I should be logged in successfully */
    public function iShouldBeLoggedInSuccessfully(): void
    {
        if (!$this->isLoggedIn) {
            throw new Exception('Login failed');
        }
    }

    /** @When I process the order */
    public function iProcessTheOrder(): void
    {
        $this->orderTotal = $this->total;
        $this->cart = [];
        $this->total = BigDecimal::zero();
    }

    /** @Then the order total should be :expected */
    public function theOrderTotalShouldBe(string $expected): void
    {
        $expectedTotal = BigDecimal::of($expected);
        if ($this->orderTotal === null || !$this->orderTotal->isEqualTo($expectedTotal)) {
            throw new Exception("Expected $expectedTotal, got $this->orderTotal");
        }
    }
}

此上下文类定义了所有三个特性的步骤,使用严格类型和 BigDecimal 实现精度。它通过购物车、总额和登录状态等属性来维护状态。

iHaveAnEmptyCart 方法将购物车和总额重置为零,为新项目做准备。它使用空数组作为购物车,并使用 zero 作为 total,确保清除。

iAddItemCosting 方法将项目及其价格添加到购物车。它将价格字符串转换为 BigDecimal,将项目存储在数组中,并通过 plus 精确加法来更新总额。

theCartTotalShouldBe 方法检查购物车总额是否与预期值匹配。它将预期字符串转换为 BigDecimal,并使用 isEqualTo 进行精确比较,如果它们不同则抛出错误。

iAmOnTheLoginPage 方法模拟导航到登录页面。它将当前页面设置为“login”,并确保用户未登录,为登录步骤提供上下文。

iEnterUsernameAndPassword 方法处理登录尝试。它检查用户是否在登录页面上,然后针对硬编码值(“user1”、“pass123”)验证凭据以简化操作。

iShouldBeLoggedInSuccessfully 方法确认登录成功。如果登录标志不为 true,则抛出异常,确保身份验证逻辑按预期工作。

iProcessTheOrder 方法将购物车总额转换为订单总额。它存储当前总额,然后重置购物车和总额,模拟真实的结帐流程。

theOrderTotalShouldBe 方法验证订单总额。它检查订单总额是否存在并且与预期的 BigDecimal 值匹配,如果任一检查失败,则抛出错误。

此上下文使用严格类型和 BigDecimal 进行精确的购物车和订单计算,以及登录逻辑。

运行 Behat 测试

使用此命令执行您的 Behat 测试

vendor/bin/behat

这会运行 features 中的所有特性文件并显示结果。

使用钩子

钩子允许您在场景周围运行代码。将它们用于设置或清理,例如在专业环境中记录测试开始。

hooks_example.php
<?php

declare(strict_types=1);

use Behat\Behat\Context\Context;

class FeatureContext implements Context
{
    private string $logFile = 'test.log';

    /** @BeforeScenario */
    public function beforeScenario(): void
    {
        file_put_contents($this->logFile, "Test started\n", FILE_APPEND);
    }

    /** @AfterScenario */
    public function afterScenario(): void
    {
        file_put_contents($this->logFile, "Test ended\n", FILE_APPEND);
    }
}

这会将测试开始和结束时间记录到文件中,以进行审计。

Behat 最佳实践

来源

Behat 文档

本教程演示了如何使用 Behat 在 PHP 中进行 BDD。它提供了一种编写与业务需求一致的清晰测试的方法,有助于协作。

作者

我叫 Jan Bodnar,是一位充满激情的程序员,拥有多年的经验。自 2007 年以来,我写了编程文章,总共超过 1400 篇,以及 8 本电子书。我拥有八年的编程教学经验。

列出 所有 PHP 教程