フレームワークは、迅速なアプリケーション開発のためのツールを提供しますが、多くの場合、機能を早く作成するほど技術的負債が増加します。開発者にとって保守性が意図的に重視されていない場合、技術的負債が発生します。単体テストと構造が不足しているため、将来の変更とデバッグにはコストがかかります。
ここでは、テスト容易性と保守性を考慮してコードの構造化を開始し、時間を節約する方法を説明します。
$_SESSION
グローバル変数に依存します。 PHPUnit などの単体テスト フレームワークはコマンド ラインに依存しており、
このコードの保守性は理想的ではありません。たとえば、データ ソースを変更する場合、アプリケーションで使用されるすべての ここでは、上記の機能の単体テストを作成する試みを示します。
リーリー
### それをチェックしよう。まず、テストは失敗します。 第二に、データベース接続設定がありません。つまり、これを機能させるには、アプリケーションをブートストラップして
App オブジェクトとその
db オブジェクトを取得する必要があります。テストするには、動作するデータベース接続も必要です。
アプリケーションで実行される CLI (PHPUnit) の構成をセットアップします
データベース接続に依存します。これを行うことは、単体テストとは別のデータ ソースに依存することを意味します。テスト データベースに期待するデータが含まれていない場合はどうすればよいでしょうか?データベース接続が遅い場合はどうすればよいでしょうか?
ブートストラップに依存するアプリケーションは、テストにオーバーヘッドが追加されるため、単体テストの速度が大幅に低下する可能性があります。理想的には、コードのほとんどは、使用されているフレームワークに関係なくテストできます。
これは、アプリケーション全体で使用できるメソッドを提供します。関数をモデルに渡す代わりに、呼び出し時に現在のユーザーを渡すことができます。セッション グローバル変数などの他の機能に依存しない場合、コードはよりモジュール化され、保守しやすくなります。
依存性の注入
依存関係の注入を追加して、この状況を改善しましょう。データベース接続をクラスに渡すと、モデルは次のようになります。
リーリーこれで、
User単体テストがどのようなものかを見てみましょう。
リーリー
この単体テストに新しいもの、Mockery を追加しました。 Mockery を使用すると、PHP オブジェクトを「モック」(偽装) できます。この例では、データベース接続をシミュレートしています。このモックを使用すると、データベース接続のテストを省略して、モデルをテストするだけです。
Mockery について詳しく知りたいですか?
この例では、SQL 接続をシミュレートしています。
select
、where、および、
limit
get メソッドの呼び出しを予期するようにモック オブジェクトに指示します。 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 中国語 Web サイトの他の関連記事を参照してください。