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

TDF*_*TDF 10 delphi mocking delphi-mocks

我开始使用Delphi-Mocks框架,并且在模拟构造函数中具有参数的类时遇到问题.TMock的类功能"Create"不允许参数.如果尝试创建TFoo.Create的模拟实例(Bar:someType); 当TObjectProxy.Create时,我得到参数计数不匹配; 试图调用T的'Create'方法.

显然这是因为以下代码没有将任何参数传递给"Invoke"方法:

instance := ctor.Invoke(rType.AsInstance.MetaclassType, []);
Run Code Online (Sandbox Code Playgroud)

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

class function Create( Args: array of TValue ): TMock<T>; overload;static;
Run Code Online (Sandbox Code Playgroud)

并且正在进行我所做的有限测试.

我的问题是:

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

谢谢

PS:我知道Delphi-Mocks是以接口为中心的,但它确实支持类,我正在研究的代码库是99%的类.

Dav*_*nan 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;
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,代码假设您的类具有无参数构造函数.当你在你的类上调用它时,它的构造函数确实有参数,这会导致运行时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;
Run Code Online (Sandbox Code Playgroud)

坦率地说,这看起来对我来说更明智.调用构造函数和析构函数肯定没有意义.

如果我在这里做得很好并且错过了重点,请告诉我.这完全有可能!