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

显然,这是因为以下代码未将任何参数传递给“Invoke”方法:

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

我创建了一个重载的类函数,该函数确实传递了参数:
class function Create( Args: array of TValue ): TMock<T>; overload;static;

并正在使用我已完成的有限测试。

我的问题是:

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

谢谢

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

最佳答案

正如我所看到的,基本问题是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;

坦白说,这对我来说似乎更明智。调用构造函数和析构函数肯定没有意义。

请让我知道我是否对此印象深刻并且错过了重点。那完全有可能!

关于Delphi-Mocks:在构造函数中模拟带有参数的类,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/15582277/

10-16 05:10
查看更多