Jira 到 BDD 到 TDD
SOLID 原则由罗伯特-C-马丁(Robert C. Martin)定义,是一套编码指南或标准,可帮助开发人员编写更有条理、解耦、可维护、可扩展的软件。在本章中,我们将逐一介绍这些原则,但我们将尝试通过在一个真实项目中工作来模拟这一过程,然后实施每一条原则。
在本章中,我们将编写尽量遵循 SOLID 原则的解决方案代码,但在此之前,我们需要解决一个示例问题。正如我们在 第 7 章 "使用 BDD 和 TDD 构建解决方案代码" 中所做的那样,我们将从 Jira 票据开始,编写一些 Gherkin 功能,编写 Behat 测试,编写集成和单元测试,然后编写遵循 SOLID 的解决方案代码,如下图所示:

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

正如我们在 第 7 章 "使用 BDD 和 TDD 构建解决方案代码" 中所做的那样,为你的 Jira ticket 创建一个新的 git 分支。从你在 第 2 章 "理解和组织项目的业务需求" 中建立的仓库中查看 git 分支,然后开始编写一些测试和程序!
在开始学习 SOLID 原则之前,首先,我们需要进行 BDD 测试,这些测试将推动我们编写解决方案代码,同时努力遵循 SOLID 原则。请记住,我们总是需要从失败的测试开始。接下来,要开始使用 BDD,我们需要先编写一个 Gherkin 功能。
Gherkin 功能
让我们首先编写一个 Gherkin 功能来描述我们期望构建的行为。在 behat
目录中创建包含以下内容的以下功能文件:
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 上下文类。请按照以下步骤操作:
-
首先,更新
behat.yml
文件:codebase/behat/behat.ymldefault: suites: default: contexts: - FeatureContext - HomeContext suite_a: contexts: - InventoryClerkRegistrationContext suite_create: contexts: - CreateToyCarRecordContext
-
更新主
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
-
现在应该在
features/bootstrap/CreateToyCarRecordContext.php
中创建一个新类。重构iAmInTheInventorySystemPage
方法,使其抛出\Exception
。 -
接下来,让我们确保可以通过运行以下命令来执行此功能测试:
/var/www/html/behat# ./vendor/bin/behat features/create_toy_car_record.feature --suite=suite_create
然后您应该看到以下测试结果:

现在,我们知道该功能的 Behat 测试可以执行,但会按预期失败,所以让我们继续讨论 Symfony 应用程序。
功能测试
我们创建的 Behat 测试已经是一个功能测试—我们还需要在 Symfony
目录中创建一个功能测试吗?我认为这是可有可无的,但它有助于我们快速运行基本的烟雾测试—例如,如果我们想快速检查控制器是否加载且没有遇到致命错误。我们不需要运行更大更慢的 Behat 测试就能发现问题:
-
使用以下内容创建以下测试类:
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(); } }
-
创建控制器测试类后,让我们运行以下命令以确保 PHPUnit 可以执行此测试并且它会失败:
/var/www/html/symfony# ./vendor/bin/phpunit --filter InventoryAdminControllerTest
运行测试后,确保测试失败。还记得红色阶段吗?
很好,我们可以暂时忘掉创建控制器了。我们继续进行集成测试。这些测试将用于开发在数据库中持久化玩具车模型的机制。
集成测试
现在,我们需要开始编写集成测试,这些测试将帮助我们编写代码来持久化或创建新的玩具车模型。通过这些测试后,我们就可以回到之前创建的 Behat 测试并确保它们通过:
-
创建以下测试类,内容如下:
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 ---”); } }
-
创建测试类后,通过运行以下命令确保 PHPUnit 可以识别新的测试类:
/var/www/html/symfony# ./vendor/bin/phpunit --filter ToyCarRepositoryTest
-
运行命令后,您应该看到熟悉且令人舒缓的 PHPUnit 失败结果:
Figure 4. Figure 8.4 – Failing processor test现在我们有了一个失败的集成测试,让我们编写代码来通过它。我们希望能将新的玩具车模型持久化到持久层,也就是我们的数据库中。我们有数据库表吗?还没有。但我们不在乎。我们可以继续编写解决方案代码。接下来,我们将尝试遵循单一责任原则(SRP)来编写解决方案代码。