10

Delphi-Mocksフレームワークの使用を開始しましたが、コンストラクターにパラメーターを持つクラスをモックする際に問題が発生しています。TMock のクラス関数「Create」では、パラメータを使用できません。TFoo.Create( Bar: someType ); のモック インスタンスを作成しようとすると、TObjectProxy.Create; のときに「パラメータ カウントの不一致」が発生します。T の 'Create' メソッドを呼び出そうとします。

これは明らかに、次のコードが "Invoke" メソッドにパラメーターを渡さないためです。

instance := ctor.Invoke(rType.AsInstance.MetaclassType, []);

パラメータを渡すオーバーロードされたクラス関数を作成しました。

class function Create( Args: array of TValue ): TMock<T>; overload;static;

私が行った限定的なテストで作業しています。

私の質問は:

これはバグですか、それとも間違っていますか?

ありがとう

PS: Delphi-Mocks がインターフェイス中心であることは知っていますが、クラスをサポートしており、私が取り組んでいるコード ベースは 99% クラスです。

4

3 に答える 3

8

基本的な問題は、TMock<T>.Createテスト対象のクラス (CUT) がインスタンス化されることです。このフレームワークは、抽象基本クラスをモックすることを想定して設計されたのではないかと思います。その場合、それをインスタンス化することは無害です。CUT の便利な抽象基本クラスを持たないレガシー コードを扱っているのではないかと思います。しかし、あなたの場合、 CUT をインスタンス化する唯一の方法は、パラメーターをコンストラクターに渡すことであり、モックの目的全体を無効にします。そして、モック化する必要があるすべてのクラスの抽象基本クラスができるまで、レガシー コード ベースを再設計するのは大変な作業になると思います。

あなたはクラスTMock<TFoo>.Createがどこにあるかを書いています。TFooこれにより、プロキシ オブジェクトが作成されます。それはで起こりTObjectProxy<T>.Createます。コードは次のようになります。

constructor TObjectProxy<T>.Create;
var
  ctx   : TRttiContext;
  rType : TRttiType;
  ctor : TRttiMethod;
  instance : TValue;
begin
  inherited;
  ctx := TRttiContext.Create;
  rType := ctx.GetType(TypeInfo(T));
  if rType = nil then
    raise EMockNoRTTIException.Create('No TypeInfo found for T');

  ctor := rType.GetMethod('Create');
  if ctor = nil then
    raise EMockException.Create('Could not find constructor Create on type ' + rType.Name);
  instance := ctor.Invoke(rType.AsInstance.MetaclassType, []);
  FInstance := instance.AsType<T>();
  FVMInterceptor := TVirtualMethodInterceptor.Create(rType.AsInstance.MetaclassType);
  FVMInterceptor.Proxify(instance.AsObject);
  FVMInterceptor.OnBefore := DoBefore;
end;

ご覧のとおり、コードは、クラスにパラメーター コンストラクターがないことを前提としています。コンストラクターにパラメーターがあるクラスでこれを呼び出すと、ランタイム RTTI 例外が発生します。

私がコードを理解しているように、クラスはその仮想メソッドをインターセプトする目的でのみインスタンス化されています。クラスをモックする目的を無効にするため、クラスに対して他に何もしたくありません。本当に必要なのは、 で操作できる適切な vtable を持つオブジェクトのインスタンスだけですTVirtualMethodInterceptor。コンストラクターを実行する必要がない、または実行したくない。たまたまパラメーターを持つコンストラクターを持つクラスをモックできるようにしたいだけです。

したがって、コンストラクターを呼び出すこのコードの代わりに、 を呼び出すように変更することをお勧めしますNewInstance。これは、操作可能な vtable を作成するために最低限必要な作業です。また、モック インスタンスを破棄しようとせず、代わりに を呼び出すように、コードを変更する必要もありますFreeInstance。モックで仮想メソッドを呼び出すだけであれば、これはすべて正常に機能します。

変更は次のようになります。

constructor TObjectProxy<T>.Create;
var
  ctx   : TRttiContext;
  rType : TRttiType;
  NewInstance : TRttiMethod;
  instance : TValue;
begin
  inherited;
  ctx := TRttiContext.Create;
  rType := ctx.GetType(TypeInfo(T));
  if rType = nil then
    raise EMockNoRTTIException.Create('No TypeInfo found for T');

  NewInstance := rType.GetMethod('NewInstance');
  if NewInstance = nil then
    raise EMockException.Create('Could not find NewInstance method on type ' + rType.Name);
  instance := NewInstance.Invoke(rType.AsInstance.MetaclassType, []);
  FInstance := instance.AsType<T>();
  FVMInterceptor := TVirtualMethodInterceptor.Create(rType.AsInstance.MetaclassType);
  FVMInterceptor.Proxify(instance.AsObject);
  FVMInterceptor.OnBefore := DoBefore;
end;

destructor TObjectProxy<T>.Destroy;
begin
  TObject(Pointer(@FInstance)^).FreeInstance;//always dispose of the instance before the interceptor.
  FVMInterceptor.Free;
  inherited;
end;

率直に言って、これは私にはもう少し賢明に見えます。コンストラクタとデストラクタを呼び出す意味はありません。

私がここでマークから外れていて、要点を逃した場合はお知らせください。それは完全に可能です!

于 2013-03-23T15:55:36.903 に答える
3

あなたのニーズを正しく理解できるかどうかはわかりませんが、おそらくこのハッキーなアプローチが役立つかもしれません. コンストラクターにパラメーターが必要なクラスがあると仮定します

type
  TMyClass = class
  public
    constructor Create(AValue: Integer);
  end;

パラメーターなしのコンストラクターとパラメーターを保持するクラス プロパティを使用して、このクラスを継承できます。

type
  TMyClassMockable = class(TMyClass)
  private
  class var
    FACreateParam: Integer;
  public
    constructor Create;
    class property ACreateParam: Integer read FACreateParam write FACreateParam;
  end;

constructor TMyClassMockable.Create;
begin
  inherited Create(ACreateParam);
end;

これで、クラス プロパティを使用してパラメーターをコンストラクターに転送できます。もちろん、継承されたクラスをモック フレームワークに渡す必要がありますが、他に何も変更されていないため、派生クラスも同様に行う必要があります。

これは、クラスがいつインスタンス化されるかを正確に知っている場合にのみ機能し、クラス プロパティに適切なパラメーターを与えることができます。

言うまでもなく、このアプローチはスレッドセーフではありません。

于 2013-03-23T15:58:44.123 に答える
0

免責事項: Delphi-Mocks についての知識はありません。

これは設計によるものだと思います。サンプル コードから、Delphi-Mocks がジェネリックを使用しているように見えます。次のように、ジェネリック パラメーターのインスタンスをインスタンス化する場合:

function TSomeClass<T>.CreateType: T;
begin
  Result := T.Create;
end;

次に、ジェネリック クラスにコンストラクター制約が必要です。

TSomeClass<T: class, constructor> = class

コンストラクター制約があるということは、渡された型にパラメーターのないコンストラクターが必要であることを意味します。

あなたはおそらく次のようなことをすることができます

TSomeClass<T: TSomeBaseMockableClass, constructor> = class

TSomeBaseMockableClass使用できる特定のコンストラクターも指定しますが、次のようになります。

フレームワークのすべてのユーザーに、特定の基本クラスからすべてのクラスを派生させることを要求することは、(控えめに言っても) 過度に制限的であり、Delphi の単一継承を考えると特にそうです。

于 2013-03-23T09:29:43.817 に答える