web-dev-qa-db-ja.com

製品テストを簡素化するために、製品とアクセスのコード構造を分離するアプローチを推奨します

製品固有のコードと製品アクセスコードを緊密に結合するコードがいくつかあります。私はそれを解く方法がわかりません。したがって、たとえば、Productをテストするには、Accessオブジェクトをモックアップする必要があります。そして多分それを回避する方法はありませんが、多分あります。私は次のようなコードを持っています:

class Product
{
    function __construct() {
        global $user;   //current logged in user object
        $id = $user->getId(); //used to make calls to DB
        $this->access = $db->query("SELECT access ... where id=$id");
        //DB returns "product access" information which is used to allow/reject access
        //code of product is tightly coupled with code for authorization
    }
}

class ProductA extends Product
{
    function __construct() {
        parent::__construct();
        if ($this->access != "all_access") doPartialAccess();
        else doFullAccess();
    }
}
//...
class ProductX extends Product
{
    function __construct() {
        parent::__construct();
        if ($this->access != "all_access") doPartialAccess();
        else doFullAccess();
    }
}

アプローチ1

私の最初の考えは、依存性注入を使用して、$user(または$accessオブジェクトを直接)をProduct。しかし待ってください...私はすべてのProduct*拡張Productでこれを行う必要があります。これを行っても、$userのインスタンス化をコード内の別の場所(Product*クラスの外)に移動するだけです。そのため、コードベースをより保守しやすくするのではなく、コンストラクターに不要なパラメーターを導入することでコードベースを混乱させるため、必ずしも最善の解決策になるかどうかはわかりません。

アプローチ2

もう1つの考えは、アクセスオブジェクト用のある種の「レジストリ」を用意することです。このレジストリにオブジェクトを配置し、そこからオブジェクトを抽出できます。しかし、待ってください。レジストリパターンはアンチパターンと見なされます。そして、私はすでにそれを使用しています-global $user!?これを行うためのオブジェクト指向の方法ではありませんが、それでもレジストリの概念です。

質問

私の長期的な目標は、クラスへのアクセスを制御するユーザー権限コードから製品コード(さまざまな計算、製品モデル文字列のアセンブルなど)を分離することです。製品アクセス構造は実際にいくつかのSQLクエリを構築するために使用されるため、製品とAccessのコードは現在ほとんど絡み合っています。製品にはアクセスが必要ですが、それらを解くことができるかどうかわかりません。 ProductにはAccessが必要であるという事実を回避することはできないと思います。おそらく、アクセスを制御する現在まばらに配置されているコード行を引き出し、それらを独自のAccessクラスに入れて、そのクラスをProductAccessなどと呼ぶ必要があります。それを回避する方法はないかもしれませんが、これを正しく行う方法が完全にはわかりません。 DIは不適切のようです(?)

私の短期的な目標は、Accessクラスをモックアップせずに、ProductAのコードをテストできるようにすることです。しかし、これは間違った目標になる可能性があります。必要なアクセスレベルを知らずに製品をテストするにはどうすればよいので、Accessをモックアップする必要があるかもしれません...

私の全体的な目標は、既存の機能を維持し、現在あるレジストリパターンを取り除きながら、クラスを可能な限り分離する方法を見つけることです。これを行う方法はありますか、それとも私の目標に欠陥がありますか?

3
Dennis

問題は、クラスの設計が少し混乱しているため、質問に簡単な答えがないことだと思います。

  • 製品には、ユーザーに関する知識やアクセス許可が含まれていてはなりません。たとえば、在庫レポートには、アクセス権を持つユーザーに関係なく、すべての製品にアクセスする必要があります。
  • おそらく、製品へのアクセス権を誰が持っているかを心配する何らかの形式のコントローラー/マネージャークラス(おそらくProductAccessManagerのような名前)を検討する必要があります。製品クラスに、どのユーザーロールにアクセスを許可するかを尋ね、リクエスターがそれらのロールの1つにあることを確認し、必要に応じてアクセスを許可または拒否する場合があります。これにより、アクセスロジックが製品自体から分離されます。

クリーンなクラス設計は、あなたが尋ねている種類の質問を回避し、一般的にテストを簡素化します。あなたのクラスのデザインをさらに考えれば、将来大きな利益が得られると思います。

1
kiwiron

この特定の問題については、あなたの2番目のアプローチに似たものをお勧めします。

サービスロケーターパターンを確認してください( これは優れたサービスロケーターですか、このサービスロケーターパターン(?)OKですか? )これは基本的に「レジストリ」と呼ばれるものです。

次に、User as aserviceをそこに配置しないでください。ほとんどの場合、Sessionのようなものが必要であり、コードで次のようなことを行います。

$ user = ServiceLocator :: get( "Session")-> currentUser();

ServiceLocatorは基本的にシングルトンでモック可能になっていると思います;)

そして、ServiceLocatorをテストでカバーすることを忘れないでください;)

フレームワークによっては、依存性注入が役立つ場合とそうでない場合があります。あなたの場合、それは適合しないようです-特に$ userはグローバルであるため、一般的にはシングルトンの説明に適合します。

0
Grzegorz