Frameworks는 신속한 애플리케이션 개발을 위한 도구를 제공하지만 기능을 더 빠르게 생성할수록 기술적 부채가 늘어나는 경우가 많습니다. 기술 부채는 유지 관리 가능성이 개발자의 의도적인 초점이 아닐 때 발생합니다. 단위 테스트 및 구조가 부족하여 향후 변경 및 디버깅에 비용이 많이 듭니다.
테스트 용이성과 유지 관리 용이성을 위해 코드 구조화를 시작하고 시간을 절약하는 방법은 다음과 같습니다.
인위적이지만 일반적인 코드부터 시작해 보겠습니다. 이는 특정 프레임워크의 모델 클래스일 수 있습니다.
으아악이 코드는 작동하지만 개선이 필요합니다.
$_SESSION
全局变量。单元测试框架(例如 PHPUnit)依赖于命令行,其中 $_SESSION
및 사용할 수 없는 다른 많은 전역 변수에 의존합니다. App::db
인스턴스에서 데이터베이스 코드를 변경해야 합니다. 또한, 현재 사용자의 정보를 원하지 않으면 어떻게 되나요? 위 기능에 대한 단위 테스트를 생성하려는 시도는 다음과 같습니다.
으아악 확인해 보겠습니다. 첫째, 테스트가 실패합니다. User
对象中使用的 $_SESSION
명령줄에서 PHP를 실행하기 때문에 단위 테스트에는 변수가 존재하지 않습니다.
둘째, 데이터베이스 연결 설정이 없습니다. 즉, 이 작업을 수행하려면 애플리케이션을 부트스트랩하여 App
对象及其 db
개체를 가져와야 합니다. 또한 테스트하려면 작동 중인 데이터베이스 연결이 필요합니다.
이 단위 테스트를 작동하려면 다음이 필요합니다.
이제 개선 방법에 대해 논의해 보겠습니다.
이 간단한 맥락에서 현재 사용자를 검색하는 기능은 불필요합니다. 이것은 인위적인 예이지만 DRY 원칙의 정신에 따라 제가 선택한 첫 번째 최적화는 이 접근 방식을 일반화하는 것이었습니다.
으아악이는 애플리케이션 전체에서 사용할 수 있는 방법을 제공합니다. 모델에 함수를 전달하는 대신 호출 시 현재 사용자를 전달할 수 있습니다. 코드는 세션 전역 변수와 같은 다른 기능에 의존하지 않을 때 더 모듈화되고 유지 관리하기 쉽습니다.
그러나 이는 여전히 예상대로 테스트 및 유지 관리될 수 없습니다. 우리는 여전히 데이터베이스 연결에 의존하고 있습니다.
일부 종속성 주입을 추가하여 이 상황을 개선하도록 돕겠습니다. 데이터베이스 연결을 클래스에 전달할 때 모델은 다음과 같습니다.
으아악이제 User
모델의 종속성이 제공됩니다. 우리 클래스는 더 이상 특정 데이터베이스 연결을 가정하지 않으며 전역 개체에 의존하지도 않습니다.
이 시점에서 우리 수업은 기본적으로 테스트 준비가 되었습니다. 선택한(주로) 데이터 소스와 사용자 ID를 전달하고 해당 호출의 결과를 테스트할 수 있습니다. 또한 별도의 데이터베이스 연결을 전환할 수도 있습니다(둘 다 동일한 데이터 검색 방법을 구현한다고 가정). 시원한.
단위 테스트가 어떤 모습인지 살펴보겠습니다.
으아악이 단위 테스트에 새로운 것을 추가했습니다: 조롱. Mockery를 사용하면 PHP 개체를 "모의"(가짜)할 수 있습니다. 이 예에서는 데이터베이스 연결을 시뮬레이션합니다. 모의를 사용하면 데이터베이스 연결 테스트를 건너뛰고 모델을 간단히 테스트할 수 있습니다.
조롱에 대해 더 알고 싶으세요?
이 예에서는 SQL 연결을 시뮬레이션하고 있습니다. 우리는 모의 객체에게 select
、where
、limit
和 get
方法。我返回 Mock 本身,以反映 SQL 连接对象如何返回自身 ($this
),从而使其方法调用“可链接”。请注意,对于 get
方法,我返回数据库调用结果 - 填充了用户数据的 stdClass
객체에 대한 호출을 예상하도록 지시합니다.
이렇게 하면 몇 가지 문제가 해결됩니다.
我们还可以做得更好。这就是它变得有趣的地方。
为了进一步改进这一点,我们可以定义并实现一个接口。考虑以下代码。
interface UserRepositoryInterface { public function getUser($user_id); } class MysqlUserRepository implements UserRepositoryInterface { protected $_db; public function __construct($db_conn) { $this->_db = $db_conn; } 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; } } class User { protected $userStore; public function __construct(UserRepositoryInterface $user) { $this->userStore = $user; } public function getUser($user_id) { return $this->userStore->getUser($user_id); } }
这里发生了一些事情。
addUser()
方法。User
模型中强制使用实现 UserInterface
的类。这样可以保证数据源始终有一个可用的 getUser()
方法,无论使用哪个数据源来实现 UserInterface
。请注意,我们的
User
对象类型提示UserInterface
在其构造函数中。这意味着实现UserInterface
的类必须传递到User
对象中。这是我们所依赖的保证 - 我们需要getUser
方法始终可用。
这样做的结果是什么?
User
类,我们可以轻松地模拟数据源。 (测试数据源的实现将是单独的单元测试的工作)。User
对象。如果您决定放弃 SQL,则只需创建一个不同的实现(例如 MongoDbUser
)并将其传递到您的 User
模型中。我们还简化了单元测试!
<?php use Mockery as m; use Fideloper\User; class ThirdUserTest extends PHPUnit_Framework_TestCase { public function testGetCurrentUserMock() { $userRepo = $this->_mockUserRepo(); $user = new User( $userRepo ); $result = $user->getUser( 1 ); $expected = new StdClass(); $expected->id = 1; $expected->username = 'fideloper'; $this->assertEquals( $result->id, $expected->id, 'User ID set correctly' ); $this->assertEquals( $result->username, $expected->username, 'Username set correctly' ); } protected function _mockUserRepo() { // Mock expected result $result = new StdClass(); $result->id = 1; $result->username = 'fideloper'; // Mock any user repository $userRepo = m::mock('Fideloper\Third\Repository\UserRepositoryInterface'); $userRepo->shouldReceive('getUser')->once()->andReturn( $result ); return $userRepo; } }
我们已经完全取消了模拟数据库连接的工作。相反,我们只是模拟数据源,并告诉它当调用 getUser
时要做什么。
但是,我们仍然可以做得更好!
考虑我们当前代码的用法:
// In some controller $user = new User( new MysqlUser( App:db->getConnection("mysql") ) ); $user->id = App::session("user->id"); $currentUser = $user->getUser($user_id);
我们的最后一步是引入容器。容器。在上面的代码中,我们需要创建并使用一堆对象来获取当前用户。此代码可能散布在您的应用程序中。如果您需要从 MySQL 切换到 MongoDB,您仍然需要编辑上述代码出现的每个位置。那几乎不是干的。容器可以解决这个问题。
容器只是“包含”一个对象或功能。它类似于应用程序中的注册表。我们可以使用容器自动实例化一个新的 User
对象以及所有需要的依赖项。下面,我使用 Pimple,一个流行的容器类。
// Somewhere in a configuration file $container = new Pimple(); $container["user"] = function() { return new User( new MysqlUser( App:db->getConnection('mysql') ) ); } // Now, in all of our controllers, we can simply write: $currentUser = $container['user']->getUser( App::session('user_id') );
我已将 User
模型的创建移至应用程序配置中的一个位置。结果是:
User
对象和选择的数据存储在我们应用程序的一个位置定义。User
模型从使用 MySQL 切换到 ONE 位置中的任何其他数据源。这更易于维护。在本教程的过程中,我们完成了以下任务:
我相信您已经注意到,我们以可维护性和可测试性的名义添加了更多代码。可以对这种实现提出强有力的论据:我们正在增加复杂性。事实上,这需要项目的主要作者和合作者对代码有更深入的了解。
但是,技术债务总体减少远远超过了解释和理解的成本。
您可以使用 Composer 轻松地将 Mockery 和 PHPUnit 包含到您的应用程序中。将这些添加到 composer.json
文件中的“require-dev”部分:
"require-dev": { "mockery/mockery": "0.8.*", "phpunit/phpunit": "3.7.*" }
然后,您可以按照“dev”要求安装基于 Composer 的依赖项:
$ php composer.phar install --dev
在 Nettuts+ 上了解有关 Mockery、Composer 和 PHPUnit 的更多信息。
对于 PHP,请考虑使用 Laravel 4,因为它特别利用了容器和此处介绍的其他概念。
感谢您的阅读!
위 내용은 테스트 및 유지 관리가 가능한 PHP 코드 생성의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!