核心要点
本文由 Younes Rafie 审核。感谢所有 SitePoint 的同行评审人员,使 SitePoint 内容达到最佳状态!
我一开始并没有为我的代码编写测试。像许多人一样,我的“测试”就是编写代码并刷新页面。“看起来对吗?”,我会问自己。如果我认为是对的,我就继续进行。
事实上,我做过的大部分工作都是在不太关心其他形式测试的公司。经过多年时间,以及像 Chris Hartjes 这样的人的明智建议,我才看到了测试的价值。而且我仍在学习好的测试是什么样的。
我最近开始从事一些包含捆绑测试观察者的 JavaScript 项目。
这是一个关于测试驱动 NodeJS 开发的精彩高级视频教程!
在 JavaScript 的世界里,预处理源代码并不少见。在 JavaScript 的世界里,开发人员使用不被广泛支持的语法编写代码,然后将代码转换为被广泛支持的语法,通常使用名为 Babel 的工具。
为了减少调用转换脚本的负担,样板项目已开始包含自动监视文件更改的脚本;然后调用这些脚本。
我参与的这些项目采用了类似的方法来重新运行单元测试。当我更改 JavaScript 文件时,这些文件会被转换,并且单元测试会被重新运行。这样,我可以立即看到是否破坏了任何东西。
本教程的代码可以在 Github 上找到。我已经用 PHP 7.1 测试过它。
自从开始从事这些项目以来,我开始为 PHPUnit 设置类似的东西。事实上,我设置 PHPUnit 观察者脚本的第一个项目是一个也预处理文件的 PHP 项目。
在我向我的项目添加预处理脚本后,这一切就开始了:
composer require pre/short-closures
这些特定的预处理脚本允许我重命名 PSR-4 自动加载的类(从 path/to/file.php ⇒ path/to/file.pre),以选择加入它们提供的功能。所以我向我的 composer.json 文件添加了以下内容:
"autoload": { "psr-4": { "App\": "src" } }, "autoload-dev": { "psr-4": { "App\Tests\": "tests" } }
这来自 composer.json
然后我添加了一个类来生成包含当前用户会话详细信息的函数:
namespace App; use Closure; class Session { private $user; public function __construct(array $user) { $this->user = $user; } public function closureWithUser(Closure $closure) { return () => { $closure($this->user); }; } }
这来自 src/Session.pre
为了检查这是否有效,我设置了一个小的示例脚本:
require_once __DIR__ . "/vendor/autoload.php"; $session = new App\Session(["id" => 1]); $closure = ($user) => { print "user: " . $user["id"] . PHP_EOL; }; $closureWithUser = $session->closureWithUser($closure); $closureWithUser();
这来自 example.pre
……而且因为我想在非 PSR-4 类中使用短闭包,我还需要设置一个加载器:
require_once __DIR__ . "/vendor/autoload.php"; Pre\Plugin\process(__DIR__ . "/example.pre");
这来自 loader.php
这段代码很多,是为了说明一个小点。Session 类有一个 closureWithUser 方法,它接受一个闭包并返回另一个。调用时,这个新的闭包将调用原始闭包,提供用户会话数组作为参数。
要运行所有这些,请在终端中键入:
php loader.php
作为旁注,这些预处理器生成的有效 PHP 语法非常漂亮。它看起来像这样:
$closure = function ($user) { print "user: " . $user["id"] . PHP_EOL; };
……和
public function closureWithUser(Closure $closure) { return [$closure = $closure ?? null, "fn" => function () use (&$closure) { $closure($this->user); }]["fn"]; }
你可能不想将 php 和 pre 文件都提交到仓库。为此,我已经将 app/**/*.php 和 examples.php 添加到 .gitignore 中。
那么我们如何测试这个呢?让我们从安装 PHPUnit 开始:
composer require --dev phpunit/phpunit
然后,我们应该创建一个配置文件:
<?xml version="1.0" encoding="UTF-8"?> <phpunit backupGlobals="false" backupStaticAttributes="false" bootstrap="vendor/autoload.php" colors="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="false" processIsolation="false" stopOnFailure="false" syntaxCheck="false" > <testsuites> <testsuite> <directory suffix="Test.php">tests</directory> </testsuite> </testsuites> <filter> <whitelist addUncoveredFilesFromWhitelist="true"> <directory suffix=".php">src</directory> </whitelist> </filter> </phpunit>
这来自 phpunit.xml
如果我们运行 vendor/bin/phpunit,它将有效。但是我们还没有任何测试。让我们做一个:
namespace App\Tests; use App\Session; use PHPUnit\Framework\TestCase; class SessionTest extends TestCase { public function testClosureIsDecorated() { $user = ["id" => 1]; $session = new Session($user); $expected = null; $closure = function($user) use (&$expected) { $expected = "user: " . $user["id"]; }; $closureWithUser = $session ->closureWithUser($closure); $closureWithUser(); $this->assertEquals("user: 1", $expected); } }
这来自 tests/SessionTest.php
当我们运行 vendor/bin/phpunit 时,单个测试通过了。耶!
到目前为止,一切顺利。我们编写了一小段代码,以及这段代码的测试。我们甚至不必担心预处理是如何工作的(比 JavaScript 项目更上一层楼)。
当我们尝试检查代码覆盖率时,问题就开始了:
vendor/bin/phpunit --coverage-html coverage
由于我们对 Session 进行了测试,因此将报告覆盖率。它是一个简单的类,因此我们已经对它实现了 100% 的覆盖率。但是如果我们添加另一个类:
namespace App; class BlackBox { public function get($key) { return $GLOBALS[$key]; } }
这来自 src/BlackBox.pre
当我们检查覆盖率时会发生什么?仍然是 100%。
发生这种情况是因为我们没有任何加载 BlackBox.pre 的测试,这意味着它从未被编译。因此,当 PHPUnit 查找已覆盖的 PHP 文件时,它看不到这个可预处理的文件。
让我们创建一个新脚本,在尝试运行测试之前构建所有 Pre 文件:
composer require pre/short-closures
这来自 tests/bootstrap.php
在这里,我们创建了 3 个函数;一个用于获取递归文件迭代器(来自路径),一个用于删除此迭代器的文件,一个用于重新编译 Pre 文件。
我们需要替换 phpunit.xml 中当前的 bootstrap 文件:
"autoload": { "psr-4": { "App\": "src" } }, "autoload-dev": { "psr-4": { "App\Tests\": "tests" } }
这来自 phpunit.xml
现在,每当我们运行测试时,此脚本将首先清理并重建所有 Pre 文件到 PHP 文件。覆盖率被正确报告,我们可以继续我们的快乐旅程……
我们的代码库很小,但它不需要很小。我们可以在真实的应用程序中尝试这个,并立即后悔每次想要测试时都必须重建文件。
在这个我提到的项目中,我有 101 个 Pre 文件。仅仅为了运行我的(希望很快的)单元测试套件,这就需要大量的预处理。我们需要一种方法来监视更改,并且只重建重要的部分。首先,让我们安装一个文件观察者:
namespace App; use Closure; class Session { private $user; public function __construct(array $user) { $this->user = $user; } public function closureWithUser(Closure $closure) { return () => { $closure($this->user); }; } }
然后,让我们创建一个测试脚本:
require_once __DIR__ . "/vendor/autoload.php"; $session = new App\Session(["id" => 1]); $closure = ($user) => { print "user: " . $user["id"] . PHP_EOL; }; $closureWithUser = $session->closureWithUser($closure); $closureWithUser();
这来自 scripts/watch-test
该脚本创建一个 Symfony 查找器(用于扫描我们的 src 和 tests 文件夹)。我们定义了一个临时更改文件,但这对于我们正在做的事情来说并不是严格要求的。我们接下来使用一个无限循环。ResourceWatcher 有一个我们可以用来查看是否创建、修改或删除了任何文件的方法。
新的,让我们找到哪些文件已更改,并重建它们:
require_once __DIR__ . "/vendor/autoload.php"; Pre\Plugin\process(__DIR__ . "/example.pre");
这来自 scripts/watch-test
这段代码类似于我们在 bootstrap 文件中所做的操作,但它只应用于已更改的文件。当文件更改时,我们还应该重新运行测试:
php loader.php
这来自 scripts/watch-test
我们正在引入几个环境变量。您可以根据自己的喜好管理这些变量,但我更喜欢将它们添加到 composer 脚本中:
$closure = function ($user) { print "user: " . $user["id"] . PHP_EOL; };
这来自 composer.json
APP_COVER 并不是那么重要。它只是告诉观察者脚本是否包含代码覆盖率。APP_REBUILD 扮演着更重要的角色:它控制在加载 tests/bootstrap.php 文件时是否重建 Pre 文件。我们需要修改该文件,以便仅在请求时重建文件:
public function closureWithUser(Closure $closure) { return [$closure = $closure ?? null, "fn" => function () use (&$closure) { $closure($this->user); }]["fn"]; }
这来自 tests/bootstrap.php
我们还需要修改观察者脚本,以便在包含 bootstrap 代码之前设置此环境变量。整个观察者脚本如下所示:
composer require --dev phpunit/phpunit
这来自 scripts/watch-test
现在我们应该能够启动它,并在每次可预处理文件更改时运行我们的测试……
需要记住几件事(rawr)。首先,您需要 chmod x scripts/* 才能运行观察者脚本。其次,您需要设置 config: {process-timeout: 0}(在 composer.json 中),否则观察者将在 300 秒后死亡。
这个测试观察者还启用了一个很酷的副作用:能够在我们的 PHPUnit 测试中使用预处理器/转换。如果我们向 tests/bootstrap.php 添加一些代码:
composer require pre/short-closures
这来自 tests/bootstrap.php
……并且我们在测试文件中启用预处理(对于 Pre,这意味着将其重命名为 .pre)。然后我们可以开始在我们的测试文件中使用相同的预处理器:
"autoload": { "psr-4": { "App\": "src" } }, "autoload-dev": { "psr-4": { "App\Tests\": "tests" } }
这来自 tests/SessionTest.pre
我不敢相信在尝试创建这种测试观察者之前,我已经做了这么多的预处理器工作。这证明了我们可以从其他语言和框架中学到什么。如果我没有参与那些 JavaScript 项目,我可能会继续在每次测试运行之前重建我的文件。恶心!
这种方法对您有效吗?它可以适应异步 HTTP 服务器或其他长期运行的进程。请在评论中告诉我们您的想法。
在 PHP 中设置 JavaScript 风格的测试观察者涉及多个步骤。首先,您需要安装 PHPUnit 和 PHPUnit-Watcher。PHPUnit 是一个用于 PHP 的测试框架,它提供了一种为代码编写测试的方法。PHPUnit-Watcher 是一个监视您的代码并在保存文件时运行 PHPUnit 测试的工具。安装这些工具后,您可以配置 PHPUnit-Watcher 来监视您的代码并自动运行您的测试。此设置允许您立即获得代码更改的反馈,这可以帮助您更快地发现和修复错误。
在 PHP 中使用测试观察者有很多好处。它提供代码更改的即时反馈,这可以帮助您更快地发现和修复错误。它还可以节省您的时间,因为您不必在每次代码更改后手动运行测试。此外,它鼓励您为代码编写测试,这可以提高代码质量并使其更易于维护。
是的,您可以在 JavaScript 函数内使用 PHP 代码,但不建议这样做。PHP 是一种服务器端语言,而 JavaScript 是一种客户端语言。这意味着 PHP 代码在页面发送到客户端之前在服务器上执行,而 JavaScript 代码在页面接收后在客户端执行。因此,如果您尝试在 JavaScript 函数内使用 PHP 代码,PHP 代码将在 JavaScript 函数之前执行,这可能会导致意外结果。
Codeception 是一个用于 PHP 的测试框架,它支持单元测试、功能测试和验收测试。要使用 Codeception 测试您的 PHP 代码,您首先需要安装 Codeception 并为您的项目配置它。然后,您可以使用 Codeception 的语法为您的代码编写测试,并使用 Codeception 的命令行工具运行您的测试。
虽然从技术上讲可以在 JavaScript 中编写 PHP 代码,但不建议这样做。PHP 是一种服务器端语言,而 JavaScript 是一种客户端语言。这意味着 PHP 代码在页面发送到客户端之前在服务器上执行,而 JavaScript 代码在页面接收后在客户端执行。因此,如果您尝试在 JavaScript 中编写 PHP 代码,PHP 代码将在 JavaScript 代码之前执行,这可能会导致意外结果。相反,最好使用 AJAX 将数据从客户端发送到服务器,反之亦然。
以上是如何在PHP中写JavaScript风格的测试观察者的详细内容。更多信息请关注PHP中文网其他相关文章!