3

私は依存性注入コンテナーの初心者であり、モッキングと組み合わせてそれらを使用して頭を悩ませようとしています。

コントローラーとリスト (モデル) があるとします。

IBlahList = interface
  property Items[AIndex: integer]: IBlah read GetItem;
end;

IController = interface
  property List: IBlahList read GetList;
end;

IController の実装は次のようになります (次のimplementaionセクションにあることに注意してください。

implementation

TController = class (TInterfacedObject, IController)
private
  FList: IBlahList;
  function GetList: IBlahList;

public
  constructor Create(const AList: IBlahList);

end;

そしてもちろん、このクラス (および IBlahList のクラス) を次のように登録しますGlobalContainer

GlobalContainer.RegisterType<TController>.Implements<IController>;

implementationTController クラスを直接参照できないように、さまざまな情報源 (とにかく Nick Hodges です!) で示唆されているように、TController をセクションに配置します。

ここで、単体テストで ICollection の実装をテストしたいとします。

procedure TestSomething
var
  LMockList: TMock<IBlahList>;
  LController: IController;
begin
  LMockList := TMock<IBlahList>.Create;

  // Oops, I can't do this, I can't access TController
  LController := TController.Create(LMockList);

end;

それで、私の質問は、TController クラスをinterfaceセクションに移動してテストできるようにする必要があるか、またはまだ見つけていないコントローラーにモック IBlahList を渡す方法が他にありますか?

4

3 に答える 3

4

実装セクションに具体的なクラスがある場合は、必要なパラメーターを持つ IController を作成するファクトリ関数を公開できます (つまり、インターフェイス セクションにそれがあります)。

インスタンス化できない実装、IMO を持つことはまったく意味がありません。

interface

...

function CreateController(AList: IBlahList): IController;

implementation

function CreateController(AList: IBlahList): IController;
begin
  Result := TController.Create(AList);
end;
于 2012-06-09T17:28:29.287 に答える
2

テストプロジェクトでもモックフレームワークを使用する必要がありますが、これらの場合、通常は「チート」implementationして、DUNIT条件変数を使用して必要な場所に移動します。

// In the real app, we want the implementation and uses clauses here.
{$IFNDEF DUNIT}  
implementation
uses
  classes;
{$ENDIF}

type
  TClassUnderTest = class(TObject)
    // ...
  end;

// In test projects it is more convenient to have the implemenation and 
// uses clauses down here.
{$IFDEF DUNIT}
implementation
uses
  classes;
{$ENDIF}

次に、テストプロジェクトでDUNIT条件付き変数が定義されていることを確認し、TClassUnderTest宣言に必要なユニットをインターフェイスセクションに移動します。後者は、永続的に実行することも、DUNITの制御下で条件付きで実行することもできます。

于 2012-06-09T16:18:41.133 に答える
2

私が言えるのは、その場合はニックの言うことを聞かないことです。

ユニットの実装部分にクラスを配置することには欠点があり、そのうちの 1 つに直面しています。

依存性注入を使用するポイントは、コードの断片を分離することです。

ここで、TController と IBlahList を実装するいくつかのクラスの静的な依存関係を削除しましたが、別の (さらに悪い imo) 依存関係、つまり DI コンテナーへの依存関係を取り込みました。

誰かが本番コードでクラスを直接作成するのを防ぐためだけに、ユニットの実装部分内にクラスを配置しないでください。また、そのユニットに DI コンテナーへの依存関係を入れないでください。

より良いアプローチは、インターフェース、クラス、登録の 3 つのユニットを持つことです。

編集: この記事を読んで、下線部に注意することをお勧めします: http://www.loosecouplings.com/2011/01/dependency-injection-using-di-container.html

Edit2 - 私が何を意味するかを示すために、いくつかの疑似コードを追加しました。単体テストコードは、質問とまったく同じである可能性があります。

unit Interfaces;

interface

type
  IBlahList = interface
    property Items[AIndex: integer]: IBlah read GetItem;
  end;

  IController = interface
    property List: IBlahList read GetList;
  end;

implementation

end.

-

unit Controller;

interface

uses
  Classes,
  Interfaces;

type
  TController = class (TInterfacedObject, IController)
  private
    FList: IBlahList;
    function GetList: IBlahList;
  public
    constructor Create(const AList: IBlahList);
  end;

implementation

...

end.

-

unit Registration;

interface

implementation

uses
  Interfaces,
  Controller,
  Spring.Container;

initialization
  GlobalContainer.RegisterType<TController>.Implements<IController>;

end.
于 2012-06-10T13:00:14.357 に答える