目录
项目设置
设置测试
我们缺少什么?
在测试之前构建所有文件
除了这件事……
奖励回合!
结论
关于 PHP 中 JavaScript 风格测试观察者的常见问题解答 (FAQ)
如何在 PHP 中设置 JavaScript 风格的测试观察者?
使用 PHP 中的测试观察者的好处是什么?
我可以在 JavaScript 函数内使用 PHP 代码吗?
如何使用 Codeception 测试我的 PHP 代码?
如何在 JavaScript 中编写 PHP 代码?
首页 后端开发 php教程 如何在PHP中写JavaScript风格的测试观察者

如何在PHP中写JavaScript风格的测试观察者

Feb 09, 2025 am 10:58 AM

How to Write JavaScript-Style Test Watchers in PHP

核心要点

  • 将 JavaScript 风格的测试观察者集成到 PHP 项目中,自动化预处理文件的转换,并在文件更改时重新运行单元测试,从而提高开发效率。
  • 使用 PHPUnit 等工具设置自动化测试,并使用 PHPUnit-Watcher 观察文件修改,确保在开发过程中立即获得反馈并检测错误。
  • 在 PHP 项目中实现预处理脚本,允许进行类似于 JavaScript Babel 的语法转换,从而提高兼容性并减少手动编码工作。
  • 配置 PHP 测试环境,以便在测试之前自动重建预处理文件,从而保持准确的代码覆盖率并简化测试过程。
  • 探索使用文件观察者的高级配置,以便有选择地仅重建已修改的文件,从而显着加快大型项目中大量文件的测试周期。

本文由 Younes Rafie 审核。感谢所有 SitePoint 的同行评审人员,使 SitePoint 内容达到最佳状态!


我一开始并没有为我的代码编写测试。像许多人一样,我的“测试”就是编写代码并刷新页面。“看起来对吗?”,我会问自己。如果我认为是对的,我就继续进行。

事实上,我做过的大部分工作都是在不太关心其他形式测试的公司。经过多年时间,以及像 Chris Hartjes 这样的人的明智建议,我才看到了测试的价值。而且我仍在学习好的测试是什么样的。

How to Write JavaScript-Style Test Watchers in PHP

我最近开始从事一些包含捆绑测试观察者的 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

现在我们应该能够启动它,并在每次可预处理文件更改时运行我们的测试……

How to Write JavaScript-Style Test Watchers in PHP

需要记住几件事(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 风格测试观察者的常见问题解答 (FAQ)

如何在 PHP 中设置 JavaScript 风格的测试观察者?

在 PHP 中设置 JavaScript 风格的测试观察者涉及多个步骤。首先,您需要安装 PHPUnit 和 PHPUnit-Watcher。PHPUnit 是一个用于 PHP 的测试框架,它提供了一种为代码编写测试的方法。PHPUnit-Watcher 是一个监视您的代码并在保存文件时运行 PHPUnit 测试的工具。安装这些工具后,您可以配置 PHPUnit-Watcher 来监视您的代码并自动运行您的测试。此设置允许您立即获得代码更改的反馈,这可以帮助您更快地发现和修复错误。

使用 PHP 中的测试观察者的好处是什么?

在 PHP 中使用测试观察者有很多好处。它提供代码更改的即时反馈,这可以帮助您更快地发现和修复错误。它还可以节省您的时间,因为您不必在每次代码更改后手动运行测试。此外,它鼓励您为代码编写测试,这可以提高代码质量并使其更易于维护。

我可以在 JavaScript 函数内使用 PHP 代码吗?

是的,您可以在 JavaScript 函数内使用 PHP 代码,但不建议这样做。PHP 是一种服务器端语言,而 JavaScript 是一种客户端语言。这意味着 PHP 代码在页面发送到客户端之前在服务器上执行,而 JavaScript 代码在页面接收后在客户端执行。因此,如果您尝试在 JavaScript 函数内使用 PHP 代码,PHP 代码将在 JavaScript 函数之前执行,这可能会导致意外结果。

如何使用 Codeception 测试我的 PHP 代码?

Codeception 是一个用于 PHP 的测试框架,它支持单元测试、功能测试和验收测试。要使用 Codeception 测试您的 PHP 代码,您首先需要安装 Codeception 并为您的项目配置它。然后,您可以使用 Codeception 的语法为您的代码编写测试,并使用 Codeception 的命令行工具运行您的测试。

如何在 JavaScript 中编写 PHP 代码?

虽然从技术上讲可以在 JavaScript 中编写 PHP 代码,但不建议这样做。PHP 是一种服务器端语言,而 JavaScript 是一种客户端语言。这意味着 PHP 代码在页面发送到客户端之前在服务器上执行,而 JavaScript 代码在页面接收后在客户端执行。因此,如果您尝试在 JavaScript 中编写 PHP 代码,PHP 代码将在 JavaScript 代码之前执行,这可能会导致意外结果。相反,最好使用 AJAX 将数据从客户端发送到服务器,反之亦然。

以上是如何在PHP中写JavaScript风格的测试观察者的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

热门话题

Java教程
1653
14
CakePHP 教程
1413
52
Laravel 教程
1305
25
PHP教程
1251
29
C# 教程
1224
24
在PHP API中说明JSON Web令牌(JWT)及其用例。 在PHP API中说明JSON Web令牌(JWT)及其用例。 Apr 05, 2025 am 12:04 AM

JWT是一种基于JSON的开放标准,用于在各方之间安全地传输信息,主要用于身份验证和信息交换。1.JWT由Header、Payload和Signature三部分组成。2.JWT的工作原理包括生成JWT、验证JWT和解析Payload三个步骤。3.在PHP中使用JWT进行身份验证时,可以生成和验证JWT,并在高级用法中包含用户角色和权限信息。4.常见错误包括签名验证失败、令牌过期和Payload过大,调试技巧包括使用调试工具和日志记录。5.性能优化和最佳实践包括使用合适的签名算法、合理设置有效期、

PHP 8.1中的枚举(枚举)是什么? PHP 8.1中的枚举(枚举)是什么? Apr 03, 2025 am 12:05 AM

PHP8.1中的枚举功能通过定义命名常量增强了代码的清晰度和类型安全性。1)枚举可以是整数、字符串或对象,提高了代码可读性和类型安全性。2)枚举基于类,支持面向对象特性,如遍历和反射。3)枚举可用于比较和赋值,确保类型安全。4)枚举支持添加方法,实现复杂逻辑。5)严格类型检查和错误处理可避免常见错误。6)枚举减少魔法值,提升可维护性,但需注意性能优化。

描述扎实的原则及其如何应用于PHP的开发。 描述扎实的原则及其如何应用于PHP的开发。 Apr 03, 2025 am 12:04 AM

SOLID原则在PHP开发中的应用包括:1.单一职责原则(SRP):每个类只负责一个功能。2.开闭原则(OCP):通过扩展而非修改实现变化。3.里氏替换原则(LSP):子类可替换基类而不影响程序正确性。4.接口隔离原则(ISP):使用细粒度接口避免依赖不使用的方法。5.依赖倒置原则(DIP):高低层次模块都依赖于抽象,通过依赖注入实现。

会话如何劫持工作,如何在PHP中减轻它? 会话如何劫持工作,如何在PHP中减轻它? Apr 06, 2025 am 12:02 AM

会话劫持可以通过以下步骤实现:1.获取会话ID,2.使用会话ID,3.保持会话活跃。在PHP中防范会话劫持的方法包括:1.使用session_regenerate_id()函数重新生成会话ID,2.通过数据库存储会话数据,3.确保所有会话数据通过HTTPS传输。

解释PHP中的晚期静态绑定(静态::)。 解释PHP中的晚期静态绑定(静态::)。 Apr 03, 2025 am 12:04 AM

静态绑定(static::)在PHP中实现晚期静态绑定(LSB),允许在静态上下文中引用调用类而非定义类。1)解析过程在运行时进行,2)在继承关系中向上查找调用类,3)可能带来性能开销。

什么是REST API设计原理? 什么是REST API设计原理? Apr 04, 2025 am 12:01 AM

RESTAPI设计原则包括资源定义、URI设计、HTTP方法使用、状态码使用、版本控制和HATEOAS。1.资源应使用名词表示并保持层次结构。2.HTTP方法应符合其语义,如GET用于获取资源。3.状态码应正确使用,如404表示资源不存在。4.版本控制可通过URI或头部实现。5.HATEOAS通过响应中的链接引导客户端操作。

您如何在PHP中有效处理异常(尝试,捕捉,最后,投掷)? 您如何在PHP中有效处理异常(尝试,捕捉,最后,投掷)? Apr 05, 2025 am 12:03 AM

在PHP中,异常处理通过try,catch,finally,和throw关键字实现。1)try块包围可能抛出异常的代码;2)catch块处理异常;3)finally块确保代码始终执行;4)throw用于手动抛出异常。这些机制帮助提升代码的健壮性和可维护性。

PHP中的匿名类是什么?您何时可以使用它们? PHP中的匿名类是什么?您何时可以使用它们? Apr 04, 2025 am 12:02 AM

匿名类在PHP中的主要作用是创建一次性使用的对象。1.匿名类允许在代码中直接定义没有名字的类,适用于临时需求。2.它们可以继承类或实现接口,增加灵活性。3.使用时需注意性能和代码可读性,避免重复定义相同的匿名类。

See all articles