首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Delphi-Mocks:使用构造函数中的参数模拟类

Delphi-Mocks:使用构造函数中的参数模拟类
EN

Stack Overflow用户
提问于 2013-03-23 09:17:49
回答 3查看 1.5K关注 0票数 10

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

很明显,这是因为下面的代码没有向"Invoke“方法传递任何参数:

代码语言:javascript
复制
instance := ctor.Invoke(rType.AsInstance.MetaclassType, []);

我已经创建了一个可以传入参数的重载类函数:

代码语言:javascript
复制
class function Create( Args: array of TValue ): TMock<T>; overload;static;

正在做我做过的有限的测试。

我的问题是:

这是一个bug,还是我做错了?

谢谢

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

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2013-03-23 23:55:37

在我看来,最基本的问题是TMock<T>.Create会导致被测试的类(CUT)被实例化。我怀疑框架是在假设您会模拟抽象基类的情况下设计的。在这种情况下,实例化它将是良性的。我怀疑您正在处理的遗留代码没有用于裁剪的方便的抽象基类。但是在您的例子中,实例化CUT的唯一方法是将参数传递给构造函数,从而违背了mocking的全部目的。我认为重新设计遗留代码库将是一项艰巨的工作,直到您为所有需要模拟的类创建抽象基类。

您正在编写TMock<TFoo>.Create,其中TFoo是一个类。这将导致创建代理对象。这发生在TObjectProxy<T>.Create中。其代码如下所示:

代码语言:javascript
复制
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异常。

根据我对代码的理解,这个类的实例化完全是为了拦截它的虚方法。我们不想对这个类做任何其他的事情,因为那会违背嘲笑它的目的。您真正需要的只是一个对象的实例,该对象具有一个可以由TVirtualMethodInterceptor操纵的合适的vtable。你不需要也不想让你的构造函数运行。你只想模拟一个恰好有一个带参数的构造函数的类。

因此,我建议您修改此代码,使其调用NewInstance,而不是调用构造函数。这是为了拥有一个可操作的vtable而需要做的最基本的事情。您还需要修改代码,使其不会试图销毁模拟实例,而是调用FreeInstance。只要您所做的只是在mock上调用虚拟方法,所有这些都会工作得很好。

修改如下所示:

代码语言:javascript
复制
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;

坦率地说,这在我看来更明智一些。调用构造函数和析构函数肯定是没有意义的。

如果我说得离题了,没有说重点,请一定要告诉我。这是完全有可能的!

票数 8
EN

Stack Overflow用户

发布于 2013-03-23 23:58:44

我不确定我是否正确地满足了您的需求,但也许这种老套的方法可能会有所帮助。假设您有一个类,它的构造函数中需要一个参数

代码语言:javascript
复制
type
  TMyClass = class
  public
    constructor Create(AValue: Integer);
  end;

可以使用无参数构造函数和保存参数的类属性继承此类

代码语言:javascript
复制
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属性将参数传递给构造函数。当然,您必须将继承的类提供给模拟框架,但由于没有其他任何更改,派生类也应该这样做。

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

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

票数 3
EN

Stack Overflow用户

发布于 2013-03-23 17:29:44

免责声明:我不了解Delphi-Mocks。

我猜这是设计好的。从您的示例代码看,Delphi-Mocks似乎使用了泛型。如果要实例化泛型参数的实例,请执行以下操作:

代码语言:javascript
复制
function TSomeClass<T>.CreateType: T;
begin
  Result := T.Create;
end;

然后在泛型类上需要一个构造函数约束:

代码语言:javascript
复制
TSomeClass<T: class, constructor> = class

具有构造函数约束意味着传入的类型必须有无参数的构造函数。

您可能会这样做:

代码语言:javascript
复制
TSomeClass<T: TSomeBaseMockableClass, constructor> = class

并为TSomeBaseMockableClass提供一个特定的构造函数,然后可以使用该构造函数:而不是

要求框架的所有用户从特定的基类派生所有类只是...好吧..。过于严格(说得委委点),特别是考虑到Delphi的单一继承。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/15582277

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档