Jira 到 BDD 到 TDD

SOLID 原则由罗伯特-C-马丁(Robert C. Martin)定义,是一套编码指南或标准,可帮助开发人员编写更有条理、解耦、可维护、可扩展的软件。在本章中,我们将逐一介绍这些原则,但我们将尝试通过在一个真实项目中工作来模拟这一过程,然后实施每一条原则。

在本章中,我们将编写尽量遵循 SOLID 原则的解决方案代码,但在此之前,我们需要解决一个示例问题。正如我们在 第 7 章 "使用 BDD 和 TDD 构建解决方案代码" 中所做的那样,我们将从 Jira 票据开始,编写一些 Gherkin 功能,编写 Behat 测试,编写集成和单元测试,然后编写遵循 SOLID 的解决方案代码,如下图所示:

image 2023 10 24 09 20 27 814
Figure 1. Figure 8.1 – Development flow

让我们使用在 第 2 章 "了解和组织项目的业务需求" 中创建的一个 Jira 票据。我们创建了一个故事,让登录用户输入并保存一些玩具车模型数据。这将是一个很好的简单功能,用来演示 SOLID 原则:

image 2023 10 24 09 22 13 749
Figure 2. Figure 8.2 – Ticket for creating toy model data

正如我们在 第 7 章 "使用 BDD 和 TDD 构建解决方案代码" 中所做的那样,为你的 Jira ticket 创建一个新的 git 分支。从你在 第 2 章 "理解和组织项目的业务需求" 中建立的仓库中查看 git 分支,然后开始编写一些测试和程序!

在开始学习 SOLID 原则之前,首先,我们需要进行 BDD 测试,这些测试将推动我们编写解决方案代码,同时努力遵循 SOLID 原则。请记住,我们总是需要从失败的测试开始。接下来,要开始使用 BDD,我们需要先编写一个 Gherkin 功能。

Gherkin 功能

让我们首先编写一个 Gherkin 功能来描述我们期望构建的行为。在 behat 目录中创建包含以下内容的以下功能文件:

codebase/behat/features/create_toy_car_record.feature
Feature: Clerk creates new toy car record
    In order to have a collection of toy car model records
    As an Inventory Clerk
    I need to be able to create a single record

    Scenario: Create new record
        Given I am in the inventory system page
        When I submit the form with correct details
        Then I should see a success message

现在我们已经有了我们的功能,让我们为其生成 Behat PHP 上下文类。

Behat context

现在,我们将采用 Gherkin 功能并为其创建一个 PHP 上下文类。请按照以下步骤操作:

  1. 首先,更新 behat.yml 文件:

    codebase/behat/behat.yml
    default:
        suites:
            default:
                contexts:
                    - FeatureContext
                    - HomeContext
            suite_a:
                contexts:
                    - InventoryClerkRegistrationContext
            suite_create:
                contexts:
                    - CreateToyCarRecordContext
  2. 更新主 behat.yml 文件后,运行以下命令来创建 PHP 上下文类:

    /var/www/html/behat# ./vendor/bin/behat --init
    /var/www/html/behat# ./vendor/bin/behat features/create_toy_car_record.feature --append-snippets -suite=suite_create
  3. 现在应该在 features/bootstrap/CreateToyCarRecordContext.php 中创建一个新类。重构 iAmInTheInventorySystemPage 方法,使其抛出 \Exception

  4. 接下来,让我们确保可以通过运行以下命令来执行此功能测试:

    /var/www/html/behat# ./vendor/bin/behat features/create_toy_car_record.feature --suite=suite_create

然后您应该看到以下测试结果:

image 2023 10 24 09 31 47 894
Figure 3. Figure 8.3 – Failed test

现在,我们知道该功能的 Behat 测试可以执行,但会按预期失败,所以让我们继续讨论 Symfony 应用程序。

功能测试

我们创建的 Behat 测试已经是一个功能测试—​我们还需要在 Symfony 目录中创建一个功能测试吗?我认为这是可有可无的,但它有助于我们快速运行基本的烟雾测试—​例如,如果我们想快速检查控制器是否加载且没有遇到致命错误。我们不需要运行更大更慢的 Behat 测试就能发现问题:

  1. 使用以下内容创建以下测试类:

    codebase/symfony/tests/Functional/Controller/InventoryAdminControllerTest.php
    <?php
    
    namespace App\Tests\Functional\Controller;
    
    use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
    
    class InventoryControllerTest extends WebTestCase
    {
        public function testCanLoadIndex(): void
        {
            $client = static::createClient();
            $client->request(‘GET’, ‘/inventory-admin’);
            $this->assertResponseIsSuccessful();
        }
    }
  2. 创建控制器测试类后,让我们运行以下命令以确保 PHPUnit 可以执行此测试并且它会失败:

    /var/www/html/symfony# ./vendor/bin/phpunit --filter InventoryAdminControllerTest

运行测试后,确保测试失败。还记得红色阶段吗?

很好,我们可以暂时忘掉创建控制器了。我们继续进行集成测试。这些测试将用于开发在数据库中持久化玩具车模型的机制。

集成测试

现在,我们需要开始编写集成测试,这些测试将帮助我们编写代码来持久化或创建新的玩具车模型。通过这些测试后,我们就可以回到之前创建的 Behat 测试并确保它们通过:

  1. 创建以下测试类,内容如下:

    codebase/symfony/tests/Integration/Processor/ToyCarProcessorTest.php
    <?php
    
    namespace App\Tests\Integration\Repository;
    
    use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
    
    class ToyCarProcessorTest extends KernelTestCase
    {
        public function testCanCreate()
        {
            $this->fail(“--- RED ---”);
        }
    }
  2. 创建测试类后,通过运行以下命令确保 PHPUnit 可以识别新的测试类:

    /var/www/html/symfony# ./vendor/bin/phpunit --filter ToyCarRepositoryTest
  3. 运行命令后,您应该看到熟悉且令人舒缓的 PHPUnit 失败结果:

    image 2023 10 24 09 51 46 459
    Figure 4. Figure 8.4 – Failing processor test

    现在我们有了一个失败的集成测试,让我们编写代码来通过它。我们希望能将新的玩具车模型持久化到持久层,也就是我们的数据库中。我们有数据库表吗?还没有。但我们不在乎。我们可以继续编写解决方案代码。接下来,我们将尝试遵循单一责任原则(SRP)来编写解决方案代码。