Laravel 中的行为驱动开发
BDD 或行为驱动开发是许多组织中流行的测试方法,并且在跨团队联合测试工作方面具有良好的记录。但问题仍然存在,我们如何在 Laravel 中实现这一点,而无需学习新的测试框架或新的语言语法,例如 Gherkin。
作为一家能够以易于阅读的方式定义流程并在我们的测试套件中表示的企业,这是一个巨大的好处。就像领域驱动设计允许我们为我们的代码创建一种普遍存在的语言一样,BDD 将使我们能够为我们的测试提供一种普遍存在的语言。
让我们通过一些示例来了解 BDD 测试可能是什么样子,然后让我们对其进行分解。让我们想象一下,我们有一个带有注册表单的 Web 应用程序。当这个表单完成后,我们期望用户会被注册,并且他们应该会自动登录。让我们在一个典型的功能测试中看看这个:
it('allows a user to register for an account', function (string $email) {
expect(
User::query()->count(),
)->toEqual(0);
post(
route('register'),
['name' => 'test', 'email' => $email, 'password' => 'password']
)->assertRedirect(route('dashboard'));
expect(
User::query()->count(),
)->toEqual(1);
})->with('emails');
这是一个简单的示例,说明您可以使用pestPHP 来测试这个端点,它复制了一个表单提交。如您所见,作为开发人员,如果您习惯于使用 Pest 进行测试,这相对容易理解。但是,您的 QA 工程师会为此苦苦挣扎,因为他们不习惯使用 PestPHP,并且它没有他们理解的语法。
我们如何重构它以使用 BDD 和我们的 QA 工程师和更广泛的团队可能理解的语法?幸运的是,pestPHP 插件将允许我们使用“Given When Then”的方法,这在 BDD 世界中很典型。这是 Give when then 插件 并且很容易上手。运行以下 composer 命令来安装这个插件:
composer require milroyfraser/pest-plugin-gwt --dev
从这里,我们可以开始为 BDD 编写特定的测试。在这一点上我们要记住的一件事是,我们是要替换我们的测试,还是要 BDD 来增强我们当前的测试套件?我希望改进我现有的测试套件以避免丢失有价值的测试。
让我们举一个我最近遇到的例子。我没有为我的 Laravel 应用程序使用任何特定的 Auth 包。相反,我需要创建一个自定义身份验证流程 - 使用一次性密码。我的注册表单是一个 Livewire 组件,它为我处理逻辑。因此,让我们首先编写功能测试以确保我们的组件正常工作。
it('will submit the form and create a new user', function (string $email) {
Livewire::test(
RegisterForm::class,
)->set(
'name', 'test',
)->set(
'email', $email,
)->set(
'password', 'password',
)->call(
'submit'
)->assertHasNoErrors(
['name', 'email', 'password']
);
})->with('emails');
我们正在测试我们是否可以填写并提交表格。我们可以围绕这一点添加我们的期望以确保在数据库中创建用户,但是我们可以在这里简化我们的功能测试并将其中的一些逻辑移动到集成测试中。
在我们的例子中,就像我的大多数代码一样,我在 Action 类中执行逻辑,因此移动它很有意义。我通常为我需要执行的所有读取和写入操作设置单个操作类,以便 CLI、Web 和 API 可以使用所有类似的逻辑——唯一的区别是它的调用方式。在上面的示例中,我们的 Livewire 组件将调用该操作来创建用户。
所以现在,让我们看一下 Gherkin 语法中的业务流程是什么样的:
Scenario: The Register Action is handled
Given the RegisterAction is created
When the handle method is called
Then a new user will be created
诚然,我们可以在标准测试中编写它,这对我们作为开发人员来说是有意义的——但我喜欢的 DDD 原则之一是你创建的无处不在的语言——几乎就像一种商业语言。
对于我们的 BDD 测试,我将在 test
下创建一个 Integration
目录,这样我就有:
单元:测试驱动开发
特征:测试驱动开发
整合:行为驱动的发展
在我们的 Integrations
目录中,我们将使用我们安装的插件存储所有作为 pestPHP 测试创建的场景。
scenario('The RegisterAction is handled')
->given(fn () => new RegisterAction())
->when(fn (RegisterAction $action) => $action->handle(
name: 'test',
email: 'pest@test.com',
password: 'password',
))->then(fn () => assertDatabaseHas('users', [
'name' => 'test',
'email' => 'pest@test.com',
]));
从上面的代码可以看出,理解起来不费吹灰之力。它与我们在大多数 BDD 测试套件中所期望的非常相似——但在框架中,我们已经习惯了。在很多情况下,我们通常可以直接将其转化为用户故事。
让我们再举一个例子,但这次我们将从一个用户故事开始:
作为用户,当我激活我的帐户时,我必须收到一封电子邮件。
现在让我们将其移至 Gherkin 语法:
场景:用户可以激活他们的帐户
给定一个新用户
当他们激活他们的帐户时
然后会发送一封电子邮件以确认激活。
最后,让我们继续使用我们正在测试的插件来使用 PestPHP:
scenario('A user can activate their account')
->given(fn (): User => User::factory()->inactive()->create())
->when(fn () => Bus::fake())
->when(fn (User $user): User => $user->activate())
->then(function (User $user) {
Bus::assertDispatched(ActivateUser::class);
});
所以你可以看到这种测试方式对你的测试套件和你的团队都有好处。我并不是说你应该总是使用这种方法——但对于那些关键的业务流程,它允许你将流程从业务理解的语言映射到你直接理解的测试套件。
你是否找到了任何其他令人兴奋的方法来改进您的应用程序中的测试策略?在 Twitter 上让我们知道您的想法!
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。