テスト可能で保守可能な PHP コードを作成する
フレームワークは、迅速なアプリケーション開発のためのツールを提供しますが、多くの場合、機能を早く作成するほど技術的負債が増加します。開発者にとって保守性が意図的に重視されていない場合、技術的負債が発生します。単体テストと構造が不足しているため、将来の変更とデバッグにはコストがかかります。
ここでは、テスト容易性と保守性を考慮してコードの構造化を開始し、時間を節約する方法を説明します。
(大まかに)
を取り上げます-
###ドライ###
- 依存性の注入 ###インターフェース### ###容器###
- 単体テストにPHPUnitを使用する
- 不自然ではあるが典型的なコードから始めましょう。これは、任意のフレームワークのモデル クラスになる可能性があります。
- リーリー このコードは機能しますが、改善の必要があります:
$_SESSION
グローバル変数に依存します。 PHPUnit などの単体テスト フレームワークはコマンド ラインに依存しており、- $_SESSION
- やその他の多くのグローバル変数は使用できません。
- 私たちはデータベース接続に依存しています。理想的には、単体テストでは実際のデータベース接続を回避する必要があります。テストはデータではなくコードに関するものです。
- インスタンスのデータベース コードを変更する必要があります。また、現在のユーザーの情報が不要な場合はどうすればよいでしょうか?
試行された単体テスト - 私たちはデータベース接続に依存しています。理想的には、単体テストでは実際のデータベース接続を回避する必要があります。テストはデータではなくコードに関するものです。
-
ここでは、上記の機能の単体テストを作成する試みを示します。
リーリー ### それをチェックしよう。まず、テストは失敗します。 User
$_SESSION
変数は、コマンド ラインから PHP を実行するため、単体テストには存在しません。第二に、データベース接続設定がありません。つまり、これを機能させるには、アプリケーションをブートストラップして
App オブジェクトとその
db オブジェクトを取得する必要があります。テストするには、動作するデータベース接続も必要です。
アプリケーションで実行される CLI (PHPUnit) の構成をセットアップします
データベース接続に依存します。これを行うことは、単体テストとは別のデータ ソースに依存することを意味します。テスト データベースに期待するデータが含まれていない場合はどうすればよいでしょうか?データベース接続が遅い場合はどうすればよいでしょうか?
ブートストラップに依存するアプリケーションは、テストにオーバーヘッドが追加されるため、単体テストの速度が大幅に低下する可能性があります。理想的には、コードのほとんどは、使用されているフレームワークに関係なくテストできます。
- それでは、これを改善する方法について話し合いましょう。
- コードをドライな状態に保ちます
- この単純なコンテキストでは、現在のユーザーを取得する関数は不要です。これは不自然な例ですが、DRY 原則の精神に基づき、私が選択した最初の最適化は、このアプローチを一般化することでした。
これは、アプリケーション全体で使用できるメソッドを提供します。関数をモデルに渡す代わりに、呼び出し時に現在のユーザーを渡すことができます。セッション グローバル変数などの他の機能に依存しない場合、コードはよりモジュール化され、保守しやすくなります。
ただし、これは依然として意図したとおりにテストおよび保守することができません。私たちは依然としてデータベース接続に依存しています。
依存性の注入
依存関係の注入を追加して、この状況を改善しましょう。データベース接続をクラスに渡すと、モデルは次のようになります。
リーリーこれで、
Userモデルの依存関係が提供されました。私たちのクラスは、特定のデータベース接続を想定しなくなり、グローバル オブジェクトにも依存しなくなりました。
この時点で、クラスは基本的にテストの準備が整いました。 (ほとんどの場合) 選択したデータ ソースとユーザー ID を渡し、その呼び出しの結果をテストできます。別々のデータベース接続を切り替えることもできます (両方が同じデータ取得方法を実装していると仮定します)。いいね。
単体テストがどのようなものかを見てみましょう。
リーリー
この単体テストに新しいもの、Mockery を追加しました。 Mockery を使用すると、PHP オブジェクトを「モック」(偽装) できます。この例では、データベース接続をシミュレートしています。このモックを使用すると、データベース接続のテストを省略して、モデルをテストするだけです。
Mockery について詳しく知りたいですか?
この例では、SQL 接続をシミュレートしています。
select
、where、および、
limit
get メソッドの呼び出しを予期するようにモック オブジェクトに指示します。 SQL 接続オブジェクトがそれ自体を返す方法 (
$this) を反映するためにモック自体を返し、そのメソッド呼び出しを「チェーン可能」にします。
get メソッドの場合、データベース呼び出しの結果、つまりユーザー データが設定された
stdClass オブジェクトを返すことに注意してください。
これによりいくつかの問題が解決されます:
- 我们仅测试我们的模型类。我们还没有测试数据库连接。
- 我们能够控制模拟数据库连接的输入和输出,因此可以可靠地测试数据库调用的结果。我知道由于模拟数据库调用,我将获得用户 ID“1”。
- 我们不需要引导我们的应用程序,也不需要提供任何配置或数据库来进行测试。
我们还可以做得更好。这就是它变得有趣的地方。
接口
为了进一步改进这一点,我们可以定义并实现一个接口。考虑以下代码。
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()
方法。 - 接下来,我们实现该接口。在本例中,我们创建一个 MySQL 实现。我们接受一个数据库连接对象,并使用它从数据库中获取用户。
- 最后,我们在
User
模型中强制使用实现UserInterface
的类。这样可以保证数据源始终有一个可用的getUser()
方法,无论使用哪个数据源来实现UserInterface
。
请注意,我们的
User
对象类型提示UserInterface
在其构造函数中。这意味着实现UserInterface
的类必须传递到User
对象中。这是我们所依赖的保证 - 我们需要getUser
方法始终可用。
这样做的结果是什么?
- 我们的代码现在完全可测试。对于
User
类,我们可以轻松地模拟数据源。 (测试数据源的实现将是单独的单元测试的工作)。 - 我们的代码更加易于维护。我们可以切换不同的数据源,而无需更改整个应用程序的代码。
- 我们可以创建任何数据源。 ArrayUser、MongoDbUser、CouchDbUser、MemoryUser 等
- 如果需要,我们可以轻松地将任何数据源传递到我们的
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 的更多信息。
- 嘲笑:更好的方法
- 使用 Composer 轻松进行包管理
- 测试驱动的 PHP
对于 PHP,请考虑使用 Laravel 4,因为它特别利用了容器和此处介绍的其他概念。
感谢您的阅读!
以上がテスト可能で保守可能な PHP コードを作成するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック









Laravelは、直感的なフラッシュメソッドを使用して、一時的なセッションデータの処理を簡素化します。これは、アプリケーション内に簡単なメッセージ、アラート、または通知を表示するのに最適です。 データは、デフォルトで次の要求のためにのみ持続します。 $リクエスト -

PHPクライアントURL(CURL)拡張機能は、開発者にとって強力なツールであり、リモートサーバーやREST APIとのシームレスな対話を可能にします。尊敬されるマルチプロトコルファイル転送ライブラリであるLibcurlを活用することにより、PHP Curlは効率的なexecuを促進します

Laravelは簡潔なHTTP応答シミュレーション構文を提供し、HTTP相互作用テストを簡素化します。このアプローチは、テストシミュレーションをより直感的にしながら、コード冗長性を大幅に削減します。 基本的な実装は、さまざまな応答タイプのショートカットを提供します。 Illuminate \ support \ facades \ httpを使用します。 http :: fake([[ 'google.com' => 'hello world'、 'github.com' => ['foo' => 'bar']、 'forge.laravel.com' =>

顧客の最も差し迫った問題にリアルタイムでインスタントソリューションを提供したいですか? ライブチャットを使用すると、顧客とのリアルタイムな会話を行い、すぐに問題を解決できます。それはあなたがあなたのカスタムにより速いサービスを提供することを可能にします

記事では、PHP 5.3で導入されたPHPの後期静的結合(LSB)について説明し、より柔軟な継承を求める静的メソッドコールのランタイム解像度を可能にします。 LSBの実用的なアプリケーションと潜在的なパフォーマ

この記事では、フレームワークにカスタム機能を追加し、アーキテクチャの理解、拡張ポイントの識別、統合とデバッグのベストプラクティスに焦点を当てています。

PHP開発でPHPのCurlライブラリを使用してJSONデータを送信すると、外部APIと対話する必要があることがよくあります。一般的な方法の1つは、Curlライブラリを使用して投稿を送信することです。
