1

単体テストで使用するインターフェイス IClient をサポートするオブジェクトをモックしています。

インターフェイス自体は、別のユニット ClientIF で定義されます。

インターフェイスは、別のユニット ldetail で定義された TDetail を参照します。

TDetail をモックすると、TDetail を使用するすべての関数で不明なエラーが発生します。

[DCC Error] MockClient.pas(16): E2003 Undeclared identifier: 'GetDetailValue'
[DCC Error] MockClient.pas(16): E2003 Undeclared identifier: 'GetDetail'
[DCC Error] MockClient.pas(16): E2003 Undeclared identifier: 'GetActiveDetail'
[DCC Fatal Error] EmailPdfPropertiesGeneratorTests.pas(25): F2063 Could not compile used unit 'MockClient.pas'

作業中のユニット/インターフェースだけをテストするには、インターフェースとサポートされている詳細オブジェクトの両方をモックできる必要があります。そうしないと、テストの悪夢となるバックエンド データにすべて関連付けられます。

関連する MockClient.pas ユニット コード

uses ClientIF, MockDetail;

TMockClient = class(TInterfacedObject, IClient)
  FDetail: TDetail;
  function GetDetailValue: TDetail;
  function GetDetail: TDetail;
  function GetActiveDetail: TDetail;
  function GetDetailName: String;
end;

関連する ClientIF.pas インターフェイス コード

uses Classes, TaxConst, OSIConst, ldetail, clNotesIF, MissingDataDefIF,
      ClientDataChangeEventIF;

    type
      IClient = interface
        ['{CFED9A10-1601-11D4-ACF6-005004889419}']
        function GetDetailValue: TDetail;
        function GetDetail: TDetail;
        function GetActiveDetail: TDetail;
        function GetDetailName: String;
      end;

関連する MockDetail.pas コード

uses Classes;

type

  TCodeValuesRec = class
    private
      fAmount: double;
      fDesc: String;
      fStateID: integer;
      fCityID: String;
      fSuffixCount: integer;
    public
      property Amount: double read fAmount write fAmount;
      property Desc: String read fDesc write fDesc;
      property StateID: integer read fStateID write fStateID;
      property CityID: String read fCityID write fCityID;
      property SuffixCount: integer read fSuffixCount write fSuffixCount;
  end;

  TDetail = class
  private
    FSeries: Integer;
    FProp: Integer;
    FPropCount: Integer;
    FCodeValuesRec: TCodeValuesRec;
  public
    property Series: Integer read FSeries write FSeries;
    property Prop: Integer read FProp write FProp;
    property PropCount: Integer read FPropCount write FPropCount;
    function GetCodeValuesRecord(WhichRecord: Integer):TCodeValuesRec;
    constructor Create;
    destructor Destroy; override;
  end;

MockClient uses 句で MockDetail を ldetail に置き換えると、コンパイルされますが、もちろん、詳細は、テスト対象のコードからの呼び出しの 1 つであるため、モックする必要があるものの 1 つです。

レガシ コードをテスト対象にしようとしていますが、これはプロセスです。この問題が発生しているコードは実際には新しいコードですが、最初はテスト用に古いオブジェクトが必要でした。

この質問の目標は、新しいコードをテスト対象にすることです。そのため、古いインターフェイス (古いクラスを含む) のモックを作成して、MyClient.GetDetail が、オブジェクトで使用できる情報が入力されたモック TDetail を返すようにします。テスト中。古いコードをリファクタリングせずに偽造する方法がない場合、プロセスは待機する必要があります。

偽の Client と Detail に偽の作業を実行させ、テスト フレームワーク (DUnit) にコンパイルして、実際の (新しい) コードに対してテストを実行できるようにできれば、それで十分です。

現在、Delphi 2010 (今年アップグレード) を使用しており、最終的には XE に移行する予定ですが、Mock Framework は XE2 でのみ動作するように見えるため、まだ使用できません。

4

1 に答える 1

2

インターフェイスに対してコーディングするか、TDetail を「実際の」クラスとモック Detail クラスの両方の (抽象) 先祖にする必要があります。そうしないと、波紋効果が発生し続けます。

使用する実際のクラスを「注入」する手段を使用して TDetail インスタンスを作成しているものを提供する方法を理解する必要があります。依存性注入を考えてみてください。ただし、依存性注入は概念であり、フレームワークではないことに注意してください。

クラス固有のコンストラクターを使用する機能を保持しながら、特定のクラスへの依存度を下げ始めるには (ほとんどの本格的な依存性注入の実装で必要とされる標準のパラメーターのないコンストラクターの代わりに)、メタクラスが助けになります。独自の特定のコンストラクターを使用してクラスをインスタンス化できます。

type
  TDetail = class(TInterfacedObject)
  end;

  TDetailClass = class of TDetail; // Meta class declaration

  function SomeFunction(const aDetailClass: TDetailClass): TDetail;
  begin
    Result := aDetailClass.Create({whatever parameters the TDetail constructor needs});
  end;

メタ クラスと TInterfacedObject の子孫を使用する場合の 1 つの注意点: TInterfacedObject には通常のコンストラクタがあり、メタ クラスによるインスタンス化が適切に機能するには、正しいコンストラクタが確実に呼び出されるように仮想コンストラクタが必要です。それは、子孫のコンストラクタオーバーライドから AfterConstruction オーバーライドにコードを移動することです。

ノート:

ロブは、子孫コンストラクターでクラス固有の作業が必要な場合にのみ仮想コンストラクターが必要であるという点で完全に正しいです。私の「アドバイス」は、この例の TDetail の詳細に基づくものではなく、依存性注入とモック化 (またはスタブ化) によって制御のより多くの反転に移行する場合に、TInterfacedObject を仮想コンストラクターに「置換」するメリットに基づいています。

ノート:

アップストリームで何も変更せずに偽物を機能させることは、難しい注文になるでしょう。ただし、Delphi のスコープ順序を使用するアプローチを回避することもできます。つまり、テスト スタブ(またはモック)にテスト対象のクラスとまったく同じ名前を付け、TDetail インスタンスを作成するユニットのスコープ内で、モック ユニットが実際の定義を含む単位:

uses 
  Client, Detail, MockClient, MockDetail;

TDetail.CreateDetail ユニットではなく MockDetail ユニットから TDetailをインスタンス化します。

于 2012-12-05T19:58:08.503 に答える