创建可测试和可维护的 PHP 代码

2023年 8月 30日 82.7k 0

创建可测试和可维护的 PHP 代码

框架提供了快速应用程序开发的工具,但通常会随着您创建功能的速度而增加技术债务。当可维护性不是开发人员有目的地关注的焦点时,就会产生技术债务。由于缺乏单元测试和结构,未来的更改和调试成本高昂。

以下是如何开始构建代码以实现可测试性和可维护性 - 并节省您的时间。

我们将(松散地)覆盖

  • 干燥
  • 依赖注入
  • 接口
  • 容器
  • 使用 PHPUnit 进行单元测试
  • 让我们从一些人为但典型的代码开始。这可能是任何给定框架中的模型类。

    class User {

    public function getCurrentUser()
    {
    $user_id = $_SESSION['user_id'];

    $user = App::db->select('id, username')
    ->where('id', $user_id)
    ->limit(1)
    ->get();

    if ( $user->num_results() > 0 )
    {
    return $user->row();
    }

    return false;
    }

    }

    登录后复制

    此代码可以工作,但需要改进:

  • 这是不可测试的。
    • 我们依赖 $_SESSION 全局变量。单元测试框架(例如 PHPUnit)依赖于命令行,其中 $_SESSION 和许多其他全局变量不可用。
    • 我们依赖数据库连接。理想情况下,在单元测试中应避免实际的数据库连接。测试是关于代码,而不是数据。
  • 此代码的可维护性并不理想。例如,如果我们更改数据源,则需要更改应用程序中使用的每个 App::db 实例中的数据库代码。另外,如果我们不想要当前用户的信息怎么办?
  • 尝试的单元测试

    这里尝试为上述功能创建单元测试。

    class UserModelTest extends PHPUnit_Framework_TestCase {

    public function testGetUser()
    {
    $user = new User();

    $currentUser = $user->getCurrentUser();

    $this->assertEquals(1, $currentUser->id);
    }

    }

    登录后复制

    让我们检查一下。首先,测试将失败。 User 对象中使用的 $_SESSION 变量在单元测试中不存在,因为它在命令行中运行 PHP。

    其次,没有数据库连接设置。这意味着,为了完成这项工作,我们需要引导我们的应用程序以获得 App 对象及其 db 对象。我们还需要一个可用的数据库连接来进行测试。

    为了使这个单元测试工作,我们需要:

  • 为我们的应用程序中运行的 CLI (PHPUnit) 设置配置
  • 依赖数据库连接。这样做意味着依赖与我们的单元测试分开的数据源。如果我们的测试数据库没有我们期望的数据怎么办?如果我们的数据库连接很慢怎么办?
  • 依赖引导的应用程序会增加测试的开销,从而显着减慢单元测试的速度。理想情况下,我们的大多数代码都可以独立于所使用的框架进行测试。
  • 那么,让我们开始讨论如何改进这一点。

    保持代码干燥

    在这个简单的上下文中,检索当前用户的函数是不必要的。这是一个人为的示例,但本着 DRY 原则的精神,我选择进行的第一个优化是概括此方法。

    class User {

    public function getUser($user_id)
    {
    $user = App::db->select('user')
    ->where('id', $user_id)
    ->limit(1)
    ->get();

    if ( $user->num_results() > 0 )
    {
    return $user->row();
    }

    return false;
    }

    }

    登录后复制

    这提供了一种我们可以在整个应用程序中使用的方法。我们可以在调用时传入当前用户,而不是将该功能传递给模型。当代码不依赖其他功能(例如会话全局变量)时,代码更加模块化和可维护。

    但是,这仍然无法按预期进行测试和维护。我们仍然依赖数据库连接。

    依赖注入

    让我们通过添加一些依赖注入来帮助改善这种情况。当我们将数据库连接传递到类中时,我们的模型可能如下所示。

    class User {

    protected $_db;

    public function __construct($db_connection)
    {
    $this->_db = $db_connection;
    }

    public function getUser($user_id)
    {
    $user = $this->_db->select('user')
    ->where('id', $user_id)
    ->limit(1)
    ->get();

    if ( $user->num_results() > 0 )
    {
    return $user->row();
    }

    return false;
    }

    }

    登录后复制

    现在,我们的 User 模型的依赖项已经提供了。我们的类不再假定某个数据库连接,也不再依赖于任何全局对象。

    至此,我们的类就基本可以测试了。我们可以传入我们选择的数据源(大部分)和用户 ID,并测试该调用的结果。我们还可以切换单独的数据库连接(假设两者都实现相同的检索数据的方法)。酷。

    让我们看看单元测试可能是什么样子。

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论