28

私はあまり単体テストを行っていないことを認めます...しかし、私はしたいと思います。そうは言っても、ユニットテストを容易にするために最適化したい非常に複雑な登録プロセスがあります。将来、より簡単にテストできるように、クラスを構造化する方法を探しています。このロジックはすべて MVC フレームワーク内に含まれているため、コントローラーはすべてのインスタンスが作成されるルートであると想定できます。

簡単にするために、私が本質的に求めているのは、CRUD 更新を使用して任意の数のサードパーティ モジュールを管理できるシステムをセットアップする方法です。これらのサードパーティ モジュールはすべて RESTful API 駆動型であり、応答データはローカル コピーに保存されます。ユーザー アカウントの削除などは、関連するすべてのモジュール (私はプロバイダーと呼んでいます) の削除をトリガーする必要があります。これらのプロバイダーは別のプロバイダーに依存している可能性があるため、削除/作成の順序は重要です。アプリケーションをサポートするために具体的にどのデザイン パターンを使用する必要があるのか​​に興味があります

登録は複数のクラスにまたがり、データを複数の db テーブルに格納します。さまざまなプロバイダーとメソッドの順序は次のとおりです (静的ではなく、簡潔にするためにそのように記述されています)

  1. Provider::create('external::create-user')特定のプロバイダーの特定のステップで登録を開始します。最初のパラメーターの二重コロン構文は、クラスが で作成をトリガーする必要があることを示しますproviderClass::providerMethod。メソッド、とProviderのインターフェースになる一般的な仮定は、他のすべてのプロバイダーがそれを実装するというものでした。これがどのようにインスタンス化されるかは、おそらくあなたが私を助ける必要があるものです.create()update()delete()
  2. $user = Provider_External::createUser()外部 API でユーザーを作成し、成功を返し、ユーザーがデータベースに保存されます。
  3. $customer = Provider_Gapps_Customer::create($user)サードパーティ API で顧客を作成し、成功を返し、ローカルに保存します。
  4. $subscription = Provider_Gapps_Subscription::create($customer)サードパーティ API で以前に作成した顧客に関連付けられたサブスクリプションを作成し、成功を返し、ローカルに保存します。
  5. Provider_Gapps_Verification::get($customer, $subscription)外部 API から行を取得します。この情報はローカルに保存されます。簡潔にするためにスキップする別の呼び出しが行われます。
  6. Provider_Gapps_Verification::verify($customer, $subscription)外部 API 検証プロセスを実行します。その結果はローカルに保存されます。

実際のコードは、少なくとも 6 つの外部 API 呼び出しと、登録時に作成された 10 を超えるローカル データベース行に依存しているため、これは非常に単純なサンプルです。コンストラクター レベルで依存性注入を使用しても意味がありません。コントローラーで 6 つのクラスをインスタンス化する必要があり、そのすべてが必要かどうかさえわからないからです。私が達成しようとしているProvider::create('external')のは、登録を開始するための開始ステップを指定するだけのようなものです。


問題の核心

ご覧のとおり、これは登録プロセスのほんの一例です。サインアップ、更新、削除などを行う必要がある数百のサービス プロバイダー (外部 API モジュール) を持つことができるシステムを構築しています。これらの各プロバイダーは、ユーザー アカウントに関連付けられます。

新規プロバイダー作成のトリガーとなる操作順序(ステップ)を指定できるようにシステムを構築したいと考えています。別の言い方をすれば、作成は非常に多くのステップにまたがることがあるため、一連のイベントで次にトリガーされるプロバイダー/メソッドの組み合わせを指定できるようにします。現在、この一連のイベントは、サブジェクト/オブザーバー パターンを介して発生しています。このコードをデータベース テーブル に移動する可能性を検討していますprovider_steps。ここでは、各ステップをリストし、(ロールバックと削除の場合)success_stepおよびfailure_step(ロールバックと削除) を示します。テーブルは次のようになります。

  # the id of the parent provider row
  provider_id int(11) unsigned primary key,
  # the short, slug name of the step for using in codebase
  step_name varchar(60),
  # the name of the method correlating to the step
  method_name varchar(120),
  # the steps that get triggered on success of this step
  # can be comma delimited; multiple steps could be triggered in parallel
  triggers_success varchar(255),
  # the steps that get triggered on failure of this step
  # can be comma delimited; multiple steps could be triggered in parallel
  triggers_failure varchar(255),
  created_at datetime,
  updated_at datetime,
  index ('provider_id', 'step_name')

ここで行う決定は非常に多くあります...継承よりも構成を優先し、いくつかのインターフェイスを作成する必要があることはわかっています。また、工場が必要になる可能性が高いことも知っています。最後に、私はここで多くのドメイン モデルのたわごとを行っているので、ビジネス ドメイン クラスが必要になる可能性があります。聖杯の追求において、完全な混乱を引き起こすことなく、それらをすべてメッシュ化する方法がわかりません.

また、データベースクエリを実行するのに最適な場所はどこですか?

各データベース テーブルのモデルは既にありますが、特定のモデル メソッドをインスタンス化する場所と方法を知りたいと思っています。

今まで読んできたもの...

4

6 に答える 6

4

ユニット テスト ユニット テストでは、ソース コードの個々のユニットを構成するコードのみをテストします。通常は、PHP のクラス メソッドまたは関数です (ユニット テストの概要)。これは、単体テストで外部 API を実際にテストしたくないことを示しています。ローカルで記述しているコードのみをテストしたいのです。ワークフロー全体をテストしたい場合は、統合テスト ( Integration Testing Overview ) を実行する必要がある可能性がありますが、これは別の獣です。

ユニット テストの設計について具体的に尋ねられたように、実際には統合テストではなくユニット テストを意味していると仮定し、プロバイダ クラスの設計には 2 つの合理的な方法があることを提出してください。

スタブ アウト (オプションで) 構成された戻り値を返すテスト double でオブジェクトを置き換える方法は、スタブと呼ばれます。スタブを使用して、「SUT が依存する実際のコンポーネントを置き換えて、テストに SUT の間接入力の制御点を持たせることができます。これにより、テストで、他の方法では実行されない可能性のあるパスに SUT を強制的に落とすことができます」。参照と例

オブジェクトのモック オブジェクト を、メソッドが呼び出されたことをアサートするなど、期待を検証するテスト double に置き換える手法は、モックと呼ばれます。

モック オブジェクトは、実行時に SUT の間接的な出力を検証するために使用される観測点として使用できます。通常、モック オブジェクトには、次の場合に SUT に値を返す必要があるという点で、テスト スタブの機能も含まれています。まだテストに失敗していませんが、間接的な出力の検証に重点が置かれています。したがって、モック オブジェクトは単なるテスト スタブとアサーション以上のものであり、根本的に異なる方法で使用されます。 参照と例

私たちのアドバイス すべてのスタブとモッキングの両方に対してクラスを設計してください。PHP ユニット マニュアルには、 Web サービスのスタブとモッキングの優れた例があります。これはすぐに使えるわけではありませんが、使用している Restful API に対して同じことを実装する方法を示しています。

データベースクエリを実行するのに最適な場所はどこですか? ORM を使用し、これを自分で解決しないことをお勧めします。PHP ORM を簡単に Google で検索し、自分のニーズに基づいて独自の決定を下すことができます。私たちのアドバイスは、Doctrineを使用することです。なぜなら、私たちは Doctrine を使用しており、それが私たちのニーズによく合っているからです。過去数年間で、Doctrine 開発者がドメインをどれだけよく知っているかを理解するようになりました。ですから喜んで彼らに任せてください。

ORM を使用する理由がよくわからない場合は、「ORM を使用する必要がある理由」を参照してくださいそして、同じ質問をグーグルで検索します。独自の ORM を展開したり、専用の担当者よりも自分でデータベース アクセスを処理したりできると感じている場合は、質問に対する答えを既に知っていると思います。自分で処理する必要があると感じた場合は、多数の ORM のソース コードを調べて ( Github の Doctrine を参照)、シナリオに最適なソリューションを見つけることをお勧めします。

楽しい質問をありがとう、ありがとう。

于 2013-06-03T13:21:05.420 に答える
2

私があなたのコードで見ることができる最も大きな問題は、実際にコードをテストすることを妨げていますが、静的クラスメソッド呼び出しを利用しています:

  • Provider::create('external::create-user')
  • $user = Provider_External::createUser()
  • $customer = Provider_Gapps_Customer::create($user)
  • $subscription = Provider_Gapps_Subscription::create($customer)
  • ...

それはあなたのコードで流行しています-たとえあなたがそれらを「簡潔さ」のために static として「のみ」概説したとしても。そのような態度は簡潔ではなく、テスト可能なコードにとって逆効果です。これらをすべて含めて避けてください。単体テストについて質問する場合、これは既知の悪い習慣であり、そのようなコードはテストが難しいことが知られています。

すべての静的呼び出しをオブジェクト メソッド呼び出しに変換し、静的グローバル状態の代わりに依存性注入を使用してオブジェクトを渡したら、PHPUnit を含む単体テストを実行できます。(単純な) テストで連携するスタブ オブジェクトとモック オブジェクトを利用する。

TODO は次のとおりです。

  1. 静的メソッド呼び出しをオブジェクト メソッド呼び出しにリファクタリングします。
  2. 依存性注入を使用してオブジェクトを渡します。

そして、コードを大幅に改善しました。それができないと主張する場合は、単体テストで時間を無駄にするのではなく、アプリケーションのメンテナンスで時間を無駄にし、迅速に出荷し、お金を稼ぎ、利益がなくなった場合はそれを燃やしてください。しかし、静的グローバル状態の単体テストでプログラミングの時間を無駄にしないでください。それは愚かなことです。

于 2013-06-10T08:28:30.643 に答える
2

クラス階層内のすべての依存関係は、外部からアクセスできる必要があります (高度に結合しないでください)。たとえば、クラス B 内でクラス A をインスタンス化する場合、クラス B には、クラス B のクラス A インスタンス ホルダーに対して実装されたセッター/ゲッター メソッドが必要です。

http://en.wikipedia.org/wiki/Dependency_injection

于 2013-06-06T02:24:05.473 に答える
1

各レイヤーに役割と責任を定義して、アプリケーションをレイヤー化することを検討してください。Apache-Axis のメッセージ フロー サブシステムからインスピレーションを得たいと思うかもしれません。コアとなる考え方は、リクエストが処理されるまでに通過するハンドラのチェーンを作成することです。このような設計により、より高次の機能を作成するために一緒にバンドルできるプラグ可能なコンポーネントが容易になります。

さらに、関与するコンポーネントを作成するために、 Functors/Function Objects、特にClosure、Predicate、Transformer、および Supplierについて読みたいと思うかもしれません。それが役立つことを願っています。

于 2013-06-11T07:45:54.667 に答える
0

状態の設計パターンを見たことがありますか? http://en.wikipedia.org/wiki/State_pattern ステート マシンですべてのステップを異なる状態にすることができ、グラフのように見えます。このグラフをデータベース テーブル/xml に保存することもできます。また、すべてのプロバイダーは、実行順序を表す独自のグラフを持つことができます。

したがって、特定の状態になると、イベントをトリガーできます (ユーザーの保存、ユーザーの取得)。アプリケーション固有のことはわかりませんが、イベントは他のプロバイダーで再利用できます。

一部のステップで失敗した場合、別のグラフ パスが実行されます。

それを正しく抽象化すると、グラフによって与えられた順序に従い、状態に基づいてイベントを実行する疎結合システムを持つことができます。

その後、他のプロバイダーを追加する必要がある場合は、グラフや新しいイベントを作成するだけで済みます。

以下に例を示します: https://github.com/Metabor/Statemachine

于 2013-06-11T18:04:55.353 に答える