框架提供了快速应用程序开发的工具,但通常会随着您创建功能的速度而增加技术债务。当可维护性不是开发人员有目的地关注的焦点时,就会产生技术债务。由于缺乏单元测试和结构,未来的更改和调试成本高昂。
以下是如何开始构建代码以实现可测试性和可维护性 - 并节省您的时间。
我们将(松散地)覆盖
让我们从一些人为但典型的代码开始。这可能是任何给定框架中的模型类。
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
对象。我们还需要一个可用的数据库连接来进行测试。
为了使这个单元测试工作,我们需要:
那么,让我们开始讨论如何改进这一点。
保持代码干燥
在这个简单的上下文中,检索当前用户的函数是不必要的。这是一个人为的示例,但本着 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,并测试该调用的结果。我们还可以切换单独的数据库连接(假设两者都实现相同的检索数据的方法)。酷。
让我们看看单元测试可能是什么样子。