在这个简短而全面的教程中,我们将了解 phpspec 的行为驱动开发 (BDD)。大多数情况下,它将介绍 phpspec 工具,但随着我们的讨论,我们将触及不同的 BDD 概念。 BDD 是当今的热门话题,phpspec 最近在 PHP 社区中获得了广泛关注。
立即学习“PHP免费学习笔记(深入)”;
BDD 旨在描述软件的行为,以便获得正确的设计。它通常与 TDD 相关,但 TDD 专注于测试您的应用程序,而 BDD 更多的是描述其行为。使用 BDD 方法将迫使您不断考虑正在构建的软件的实际需求和期望的行为。
立即学习“PHP免费学习笔记(深入)”;
最近,两个 BDD 工具在 PHP 社区中获得了广泛关注,Behat 和 phpspec。 Behat 可帮助您使用可读的 Gherkin 语言描述应用程序的外部行为。另一方面,phpspec 通过用 PHP 语言编写小的“规范”来帮助您描述应用程序的内部行为 - 因此是 SpecBDD。这些规范正在测试您的代码是否具有所需的行为。
立即学习“PHP免费学习笔记(深入)”;
在本教程中,我们将介绍与 phpspec 入门相关的所有内容。在此过程中,我们将使用 SpecBDD 方法逐步构建待办事项列表应用程序的基础。在我们前进的过程中,我们将让 phpspec 引领我们!
立即学习“PHP免费学习笔记(深入)”;
注意:这是一篇关于 PHP 的中级文章。我假设您已经很好地掌握了面向对象的 PHP。
对于本教程,我假设您已启动并运行以下内容:
立即学习“PHP免费学习笔记(深入)”;
通过 Composer 安装 phpspec 是最简单的方法。您所要做的就是在终端中运行以下命令:
$ composer require phpspec/phpspec Please provide a version constraint for the phpspec/phpspec requirement: 2.0.*@dev
这将为您创建一个 composer.json 文件,并将 phpspec 安装在 vendor/ 目录中。
为了确保一切正常,请运行 phpspec 并查看您获得以下输出:
$ vendor/bin/phpspec run 0 specs 0 examples 0ms
在开始之前,我们需要做一些配置。当 phpspec 运行时,它会查找名为 phpspec.yml 的 YAML 文件。由于我们将把代码放在命名空间中,因此我们需要确保 phpspec 知道这一点。另外,在我们这样做的同时,让我们确保我们的规格在运行时看起来很漂亮。
立即学习“PHP免费学习笔记(深入)”;
继续创建包含以下内容的文件:
formatter.name: pretty suites: todo_suite: namespace: PetersuhmTodo
还有许多其他可用的配置选项,您可以在文档中阅读。
我们需要做的另一件事是告诉 Composer 如何自动加载我们的代码。 phpspec 将使用 Composer 的自动加载器,因此这是我们规范运行所必需的。
立即学习“PHP免费学习笔记(深入)”;
将自动加载元素添加到 Composer 为您创建的 composer.json 文件中:
{ "require": { "phpspec/phpspec": "2.0.*@dev" }, "autoload": { "psr-0": { "Petersuhm\Todo": "src" } } }
运行 composer dump-autoload 将在此更改后更新自动加载器。
现在我们准备编写我们的第一个规范。我们首先描述一个名为 TaskCollection 的类。我们将使用 describe 命令(或者简短版本 desc)让 phpspec 为我们生成一个规范类。
$ vendor/bin/phpspec describe "PetersuhmTodoTaskCollection" $ vendor/bin/phpspec run Do you want me to create `PetersuhmTodoTaskCollection` for you? y
那么这里发生了什么?首先,我们要求 phpspec 为 TaskCollection 创建规范。其次,我们运行我们的规范套件,然后 phpspec 自动为我们提供创建实际的 TaskCollection 类。很酷,不是吗?
继续并再次运行该套件,您将看到我们的规范中已经有一个示例(我们稍后将看到示例是什么):
$ vendor/bin/phpspec run PetersuhmTodoTaskCollection 10 ✔ is initializable 1 specs 1 examples (1 passed) 7ms
从此输出中,我们可以看到 TaskCollection 已初始化。这是关于什么的?看一下phpspec生成的spec文件,应该就更清楚了:
<?php namespace specPetersuhmTodo; use PhpSpecObjectBehavior; use ProphecyArgument; class TaskCollectionSpec extends ObjectBehavior { function it_is_initializable() { $this->shouldHaveType('PetersuhmTodoTaskCollection'); } }
短语“可初始化”源自名为 it_is_initializes() 的函数,该函数 phpspec 已添加到名为 TaskCollectionSpec 的类中。这个函数就是我们所说的示例。在这个特定的示例中,我们有一个称为 shouldHaveType() 的匹配器,它检查 TaskCollection 的类型。如果将传递给该函数的参数更改为其他参数并再次运行规范,您将看到它将失败。在完全理解这一点之前,我认为我们需要研究变量 $this 在我们的规范中指的是什么。
当然,$this 指的是 TaskCollectionSpec 类的实例,因为这只是常规 PHP 代码。但是对于 phpspec,您必须将 $this 与您通常所做的不同,因为在幕后,它实际上指的是被测试的对象,这实际上是 TaskCollection 类。此行为继承自类 ObjectBehavior,它确保函数调用被代理到指定的类。这意味着 SomeClassSpec 将代理方法调用到 SomeClass 的实例。 phpspec 将包装这些方法调用,以便针对您刚刚看到的匹配器运行它们的返回值。
立即学习“PHP免费学习笔记(深入)”;
为了使用 phpspec,您不需要深入了解这一点,只需记住,就您而言,$this 实际上指的是被测试的对象。
立即学习“PHP免费学习笔记(深入)”;
到目前为止,我们自己还没有做任何事情。但是 phpspec 制作了一个空的 TaskCollection 类供我们使用。现在是时候填写一些代码并使此类变得有用了。我们将添加两个方法:一个 add() 方法,用于添加任务;一个 count() 方法,用于计算集合中的任务数量。
立即学习“PHP免费学习笔记(深入)”;
在编写任何实际代码之前,我们应该在规范中编写一个示例。在我们的示例中,我们想要尝试将任务添加到集合中,然后确保该任务确实已添加。为此,我们需要一个(目前还不存在的)Task 类的实例。如果我们将此依赖项作为参数添加到我们的spec函数中,phpspec将自动为我们提供一个可以使用的实例。实际上,该实例并不是真正的实例,而是 phpspec 所说的 Collaborator。该对象将充当真实对象,但 phpspec 允许我们用它做更多奇特的事情,我们很快就会看到。尽管 Task 类尚不存在,但现在就假装它存在。打开 TaskCollectionSpec 并为 Task 类添加 use 语句,然后添加示例 it_adds_a_task_to_the_collection() :
use PetersuhmTodoTask; ... function it_adds_a_task_to_the_collection(Task $task) { $this->add($task); $this->tasks[0]->shouldBe($task); }
在我们的示例中,我们编写了“我们希望有”的代码。我们调用 add() 方法,然后尝试给它一个 $task。然后我们检查该任务实际上是否已添加到实例变量 $tasks 中。匹配器 shouldBe() 是一个身份 匹配器,类似于 PHP === 比较器。您可以使用 shouldBe()、shouldBeEqualTo()、shouldEqual() 或 shouldReturn() - 他们都做同样的事情。
运行 phpspec 会产生一些错误,因为我们还没有名为 Task 的类。
立即学习“PHP免费学习笔记(深入)”;
让 phpspec 帮我们解决这个问题:
$ vendor/bin/phpspec describe "PetersuhmTodoTask" $ vendor/bin/phpspec run Do you want me to create `PetersuhmTodoTask` for you? y
再次运行 phpspec,发生了一些有趣的事情:
$ vendor/bin/phpspec run Do you want me to create `PetersuhmTodoTaskCollection::add()` for you? y
完美!如果你看一下 TaskCollection.php 文件,你会发现 phpspec 制作了一个 add() 函数供我们填写:
<?php namespace PetersuhmTodo; class TaskCollection { public function add($argument1) { // TODO: write logic here } }
尽管如此,phpspec 仍然在抱怨。我们没有 $tasks 数组,所以让我们创建一个数组并向其中添加任务:
<?php namespace PetersuhmTodo; class TaskCollection { public $tasks; public function add(Task $task) { $this->tasks[] = $task; } }
现在我们的规格都很好而且绿色。请注意,我确保输入了 $task 参数。
为了确保我们做得正确,让我们添加另一个任务:
function it_adds_a_task_to_the_collection(Task $task, Task $anotherTask) { $this->add($task); $this->tasks[0]->shouldBe($task); $this->add($anotherTask); $this->tasks[1]->shouldBe($anotherTask); }
运行 phpspec,看起来一切都很好。
我们想知道一个集合中有多少个任务,这是使用标准 PHP 库 (SPL) 中的一个接口(即 Countable 接口)的一个重要原因。此接口规定实现它的类必须具有 count() 方法。
立即学习“PHP免费学习笔记(深入)”;
之前,我们使用了匹配器 shouldHaveType(),它是一个类型匹配器。它使用 PHP 比较器 instanceof 来验证对象实际上是给定类的实例。有 4 个类型匹配器,它们的作用都相同。其中之一是 shouldImplement(),它非常适合我们的目的,所以让我们继续在示例中使用它:
function it_is_countable() { $this->shouldImplement('Countable'); }
看到这句话读起来多漂亮了吗?让我们运行该示例并让 phpspec 为我们引路:
$ vendor/bin/phpspec run Petersuhm/Todo/TaskCollection 25 ✘ is countable expected an instance of Countable, but got [obj:PetersuhmTodoTaskCollection].
好吧,我们的类不是 Countable 的实例,因为我们还没有实现它。让我们更新 TaskCollection 类的代码:
class TaskCollection implements Countable
我们的测试将无法运行,因为 Countable 接口有一个抽象方法 count(),我们必须实现该方法。现在,一个空方法就可以解决问题:
public function count() { // ... }
我们又回到了绿色。目前,我们的 count() 方法没有做太多事情,而且实际上没什么用。让我们为我们希望它具有的行为编写一个规范。首先,在没有任务的情况下,我们的计数函数预计返回零:
function it_counts_elements_of_the_collection() { $this->count()->shouldReturn(0); }
它返回 null,而不是 0。为了获得绿色测试,让我们用 TDD/BDD 方式解决这个问题:
public function count() { return 0; }
我们是绿色的,一切都很好,但这可能不是我们想要的行为。相反,让我们扩展规范并向 $tasks 数组添加一些内容:
function it_counts_elements_of_the_collection() { $this->count()->shouldReturn(0); $this->tasks = ['foo']; $this->count()->shouldReturn(1); }
当然,我们的代码仍然返回 0,并且我们有一个红色步骤。解决这个问题并不太困难,我们的 TaskCollection 类现在应该如下所示:
<?php namespace PetersuhmTodo; class TaskCollection implements Countable { public $tasks; public function add(Task $task) { $this->tasks[] = $task; } public function count() { return count($this->tasks); } }
我们进行了绿色测试,我们的 count() 方法有效。多么美好的一天!
还记得我告诉过你,phpspec 允许你使用 Collaborator 类的实例(又名由 phpspec 自动注入的实例)做一些很酷的事情吗?如果您以前编写过单元测试,您就会知道模拟和存根是什么。如果没有,请不要太担心。这只是行话。这些东西指的是“假”对象,它们将充当您的真实对象,但允许您单独进行测试。如果您的规范中需要,phpspec 会自动将这些 Collaborator 实例转换为模拟和存根。
这真是太棒了。在底层,phpspec 使用 Prophecy 库,这是一个高度固执己见的模拟框架,与 phpspec 配合得很好(并且是由同样出色的人构建的)。您可以对协作者设置期望(模拟),例如“这个方法应该被调用”,并且您可以添加承诺(存根),例如“这个方法将返回”这个值”。有了 phpspec,这真的很容易,接下来我们将完成这两件事。
立即学习“PHP免费学习笔记(深入)”;
让我们创建一个类,我们将其命名为 TodoList,它可以使用我们的集合类。
$ vendor/bin/phpspec desc "PetersuhmTodoTodoList" $ vendor/bin/phpspec run Do you want me to create `PetersuhmTodoTodoList` for you? y
我们要添加的第一个示例是用于添加任务的示例。我们将创建一个 addTask() 方法,该方法只不过是向我们的集合添加一个任务。它只是将调用定向到集合上的 add() 方法,因此这是利用期望的完美位置。我们不希望该方法实际调用 add() 方法,我们只是想确保它尝试执行此操作。此外,我们希望确保它只调用一次。看看我们如何使用 phpspec 来解决这个问题:
<?php namespace specPetersuhmTodo; use PhpSpecObjectBehavior; use ProphecyArgument; use PetersuhmTodoTaskCollection; use PetersuhmTodoTask; class TodoListSpec extends ObjectBehavior { function it_is_initializable() { $this->shouldHaveType('PetersuhmTodoTodoList'); } function it_adds_a_task_to_the_list(TaskCollection $tasks, Task $task) { $tasks->add($task)->shouldBeCalledTimes(1); $this->tasks = $tasks; $this->addTask($task); } }
首先,我们让 phpspec 为我们提供了我们需要的两个协作者:一个任务集合和一个任务。然后,我们对任务收集协作者设置一个期望,基本上是这样的:“add() 方法应该以变量 $task 作为参数调用恰好 1 次” 。这就是我们如何准备我们的协作者(现在是一个模拟),然后将其分配给 TodoList 上的 $tasks 属性。最后,我们尝试实际调用 addTask() 方法。
好吧,phpspec 对此有何评论:
$ vendor/bin/phpspec run Petersuhm/Todo/TodoList 17 ! adds a task to the list property tasks not found.
$tasks 属性不存在 - 简单的一个:
<?php namespace PetersuhmTodo; class TodoList { public $tasks; }
再试一次,让 phpspec 指导我们:
$ vendor/bin/phpspec run Do you want me to create `PetersuhmTodoTodoList::addTask()` for you? y $ vendor/bin/phpspec run Petersuhm/Todo/TodoList 17 ✘ adds a task to the list some predictions failed: DoublePetersuhmTodoTaskCollectionP4: Expected exactly 1 calls that match: DoublePetersuhmTodoTaskCollectionP4->add(exact(DoublePetersuhmTodoTaskP3:000000002544d76d0000000059fcae53)) but none were made.
好吧,现在发生了一些有趣的事情。看到消息“预计有 1 个匹配的呼叫:...”?这是我们失败的期望。发生这种情况是因为在调用 addTask() 方法后,集合上的 add() 方法没有被调用,这正是我们所期望的是。
为了恢复绿色,请在空的 addTask() 方法中填写以下代码:
<?php namespace PetersuhmTodo; class TodoList { public $tasks; public function addTask(Task $task) { $this->tasks->add($task); } }
回到绿色!感觉不错吧?
我们也来看看 Promise。我们需要一个方法来告诉我们集合中是否有任何任务。为此,我们只需检查集合上 count() 方法的返回值。同样,我们不需要具有真实 count() 方法的真实实例。我们只需要确保我们的代码调用一些 count() 方法并根据返回值执行一些操作。
立即学习“PHP免费学习笔记(深入)”;
看一下下面的示例:
function it_checks_whether_it_has_any_tasks(TaskCollection $tasks) { $tasks->count()->willReturn(0); $this->tasks = $tasks; $this->hasTasks()->shouldReturn(false); }
我们有一个任务收集协作者,它有一个 count() 方法,将返回零。这是我们的承诺。这意味着每次有人调用 count() 方法时,它都会返回零。然后,我们将准备好的协作者分配给对象的 $tasks 属性。最后,我们尝试调用一个方法 hasTasks(),并确保它返回 false。
phspec 对此有什么看法?
$ vendor/bin/phpspec run Do you want me to create `PetersuhmTodoTodoList::hasTasks()` for you? y $ vendor/bin/phpspec run Petersuhm/Todo/TodoList 25 ✘ checks whether it has any tasks expected false, but got null.
酷。 phpspec 为我们创建了一个 hasTasks() 方法,毫不奇怪,它返回 null,而不是 false。
再一次强调,这是一个很容易解决的问题:
public function hasTasks() { return false; }
我们又回到了绿色,但这并不是我们想要的。当任务有 20 个时,我们来检查一下。这应该返回 true:
function it_checks_whether_it_has_any_tasks(TaskCollection $tasks) { $tasks->count()->willReturn(0); $this->tasks = $tasks; $this->hasTasks()->shouldReturn(false); $tasks->count()->willReturn(20); $this->tasks = $tasks; $this->hasTasks()->shouldReturn(true); }
运行 phspec 我们会得到:
$ vendor/bin/phpspec run Petersuhm/Todo/TodoList 25 ✘ checks whether it has any tasks expected true, but got false.
好吧,false 不是 true,所以我们需要改进我们的代码。让我们使用 count() 方法来查看是否有任务:
public function hasTasks() { if ($this->tasks->count() > 0) return true; return false; }
哒哒!回到绿色!
编写好的规范的一部分是使其尽可能具有可读性。由于 phpspec 的自定义匹配器,我们的最后一个示例实际上可以得到一点点改进。实现自定义匹配器很容易 - 我们所要做的就是覆盖从 ObjectBehavior 继承的 getMatchers() 方法。通过实现两个自定义匹配器,我们的规范可以更改为如下所示:
function it_checks_whether_it_has_any_tasks(TaskCollection $tasks) { $tasks->count()->willReturn(0); $this->tasks = $tasks; $this->hasTasks()->shouldBeFalse(); $tasks->count()->willReturn(20); $this->tasks = $tasks; $this->hasTasks()->shouldBeTrue(); } function getMatchers() { return [ 'beTrue' => function($subject) { return $subject === true; }, 'beFalse' => function($subject) { return $subject === false; }, ]; }
我觉得这个看起来不错。请记住,重构您的规范对于使其保持最新状态非常重要。实现您自己的自定义匹配器可以清理您的规范并使其更具可读性。
实际上,我们也可以使用匹配器的否定:
function it_checks_whether_it_has_any_tasks(TaskCollection $tasks) { $tasks->count()->willReturn(0); $this->tasks = $tasks; $this->hasTasks()->shouldNotBeTrue(); $tasks->count()->willReturn(20); $this->tasks = $tasks; $this->hasTasks()->shouldNotBeFalse(); }
是的。非常酷!
我们所有的规范都是绿色的,看看它们如何很好地记录我们的代码!
PetersuhmTodoTaskCollection 10 ✔ is initializable 15 ✔ adds a task to the collection 24 ✔ is countable 29 ✔ counts elements of the collection PetersuhmTodoTask 10 ✔ is initializable PetersuhmTodoTodoList 11 ✔ is initializable 16 ✔ adds a task to the list 24 ✔ checks whether it has any tasks 3 specs 8 examples (8 passed) 16ms
我们已经有效地描述并实现了代码的预期行为。更不用说,我们的代码 100% 符合我们的规范,这意味着重构不会是一种令人恐惧的体验。
通过跟随,我希望您能受到启发来尝试 phpspec。它不仅仅是一个测试工具——它还是一个设计工具。一旦您习惯了使用 phpspec(及其出色的代码生成工具),您将很难再次放弃它!人们经常抱怨 TDD 或 BDD 降低了他们的速度。将 phpspec 纳入我的工作流程后,我确实感觉到了相反的情况 - 我的生产力显着提高。而且我的代码更扎实!
以上就是Phpspec:初学者入门指南的详细内容,更多请关注php中文网其它相关文章!
PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号