Delphi Mocks:在构造函数中使用参数模拟类

Delphi Mocks:在构造函数中使用参数模拟类,delphi,mocking,delphi-mocks,Delphi,Mocking,Delphi Mocks,我开始使用这个框架,但在模拟构造函数中有参数的类时遇到了麻烦。TMock的类函数“Create”不允许使用参数。如果尝试创建TFoo.create(Bar:someType)的模拟实例;我在TObjectProxy.Create时得到一个参数计数不匹配;尝试调用T的“Create”方法 显然,这是因为以下代码没有向“Invoke”方法传递任何参数: 我创建了一个重载类函数,该函数传递参数: class function Create( Args: array of TValue ): TMock

我开始使用这个框架,但在模拟构造函数中有参数的类时遇到了麻烦。TMock的类函数“Create”不允许使用参数。如果尝试创建TFoo.create(Bar:someType)的模拟实例;我在TObjectProxy.Create时得到一个参数计数不匹配;尝试调用T的“Create”方法

显然,这是因为以下代码没有向“Invoke”方法传递任何参数:

我创建了一个重载类函数,该函数传递参数:

class function Create( Args: array of TValue ): TMock<T>; overload;static;
class函数Create(Args:TValue数组):TMock;超载;静止的
并且正在进行我所做的有限测试

我的问题是:

这是一个错误还是我做错了

谢谢


PS:我知道Delphi Mocks是以接口为中心的,但它确实支持类,而且我正在处理的代码库中99%是类。

免责声明:我对Delphi Mocks一无所知

我想这是故意的。从您的示例代码来看,Delphi Mocks似乎在使用泛型。如果要实例化泛型参数的实例,如中所示:

function TSomeClass<T>.CreateType: T;
begin
  Result := T.Create;
end;
函数TSomeClass.CreateType:T;
开始
结果:=T.Create;
结束;
然后您需要泛型类上的构造函数约束:

TSomeClass<T: class, constructor> = class
TSomeClass=class
具有构造函数约束意味着传入的类型必须具有无参数构造函数

你可能会做类似的事情

TSomeClass<T: TSomeBaseMockableClass, constructor> = class
TSomeClass=class
并给
TSomeBaseMockableClass
一个可以使用的特定构造函数,但是


要求框架的所有用户从特定的基类派生出他们的所有类只是。。。好。。。过度限制(说得温和一点),尤其是考虑到Delphi的单一继承。

在我看来,基本问题是
TMock.Create
导致被测试的类(CUT)被实例化。我怀疑该框架是在假设您将模拟抽象基类的情况下设计的。在这种情况下,实例化它将是良性的。我怀疑您正在处理的遗留代码没有方便的抽象基类。但在您的例子中,实例化CUT的唯一方法是将参数传递给构造函数,因此无法实现模拟的全部目的。我认为重新设计遗留代码库需要做大量的工作,直到为所有需要模拟的类建立一个抽象基类

您正在编写
TMock.Create
,其中
TFoo
是一个类。这将导致创建代理对象。这发生在
TObjectProxy.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;
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;
坦白地说,我觉得这更合理一些。调用构造函数和析构函数毫无意义


请让我知道,如果我在这里偏离了目标,没有抓住要点。那完全有可能

我不确定我是否正确地理解了您的需求,但这种黑客方法可能会有所帮助。假设您有一个类在其构造函数中需要一个参数

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;
现在可以使用class属性将参数传递给构造函数。当然,您必须将继承的类赋予mock框架,但是由于没有其他改变,派生类也应该这样做

只有当您确切地知道类何时被实例化,以便能够为类属性提供适当的参数时,这才起作用


不用说,这种方法不是线程安全的。

它不是泛型。代码使用RTTI调用构造函数。它假定它的名称为
Create
。如果代码需要,它可以在调用
TRttiMethod
实例上的
Invoke
时非常轻松地传递参数。@DavidHeffernan-Aha。但即使不是泛型,框架如何知道要传递哪些参数和值?Rtti可以确定参数的数量和类型,但框架仍然不知道这些参数的含义。如果希望框架实例化类,则需要“泛型”构造函数:无参数构造函数和/或接收TValue(或Variant)数组的构造函数;或者,您必须在模拟框架上有一个通用方法,为其提供一个TValue数组,以便按规范顺序传递到构造函数的特定参数中。您只需将参数传递到
TMock.Create
TValue
的开放数组。但对我来说,我无法理解为什么要使用mock来创建真实类的实例。“这对我来说毫无意义。”大卫没有举例说明真正的类,而是举例说明它的模拟类。如果真实的类有构造函数参数,您可能需要某种方法来确保mock也可以使用它们。请注意,我不使用嘲弄(而是存根),所以我可能有点离谱。然而,两者都非常依赖依赖于依赖注入(不希望增加增强RTTI负担的框架也需要无参数构造函数)。如果您没有DI,测试c/下的类将创建您想要模拟/存根的实例。如果你有特定的构造器…@marjannema,谢谢。由于我使用的是“某种程度上”遗留的代码库,因此我确实有一些特定的类,这些类的构造函数需要参数。如果您试图模拟一个类,为什么要创建您要模拟的类的实例。毫无疑问,模拟的全部意义在于,你可以模拟这个类。当你做
TMock.Create
时,模拟框架会创建
TFoo
的一个实例。也许我不懂嘲弄,但我想重点是你创造了一些
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;