如何在 PascalMock 中模拟带有开放数组参数的方法?

发布于 2024-09-02 09:59:49 字数 1539 浏览 3 评论 0原文

我目前正在开始进行单元测试和模拟,我偶然发现了以下方法,我似乎无法为其构建有效的模拟实现:

function GetInstance(const AIID: TGUID; 
                       out AInstance; 
                     const AArgs: array of const; 
                     const AContextID: TImplContextID = CID_DEFAULT): Boolean;

(TImplContextID只是 Integer 的类型别名)

这就是我得到的结果:

function TImplementationProviderMock.GetInstance(
  const AIID: TGUID;
    out AInstance;
  const AArgs: array of const;
  const AContextID: TImplContextID): Boolean;
var
  lCall: TMockMethod;
begin
  lCall := AddCall('GetInstance').WithParams([@AIID, AContextID]);
  Pointer(AInstance) := FindVarData(lCall.OutParams[0]).VPointer;
  Result := lCall.ReturnValue;
end;

但我无法弄清楚应该如何模拟开放数组参数 AArgs。有什么想法吗?

另外,是否有可能有一种更简单的方法来返回 out 参数 AInstance 并使用 @ -notation 作为 TGUID 类型参数(本质上是一条记录,即值类型)正确的做法是什么?

是否可以使用当前版本的 PascalMock 来模拟此方法?


更新 2:为了清楚起见,我现在删减了问题文本。最初,它包含以下模拟方法的错误实现,这就是 Mason 的回复所指的:

function TImplementationProviderMock.GetInstance(
  const AIID: TGUID;
    out AInstance;
  const AArgs: array of const;
  const AContextID: TImplContextID): Boolean;
begin
  Result := AddCall('GetInstance')
           .WithParams([@AIID, AContextID])
           .ReturnsOutParams([AInstance])
           .ReturnValue;
end;

在此编译器抱怨 .ReturnsOutParams([AInstance]) 说“变量类型数组构造函数中的参数类型错误” .”。

I'm currently in the process of getting started with unit testing and mocking for good and I stumbled over the following method that I can't seem to fabricate a working mock implementation for:

function GetInstance(const AIID: TGUID; 
                       out AInstance; 
                     const AArgs: array of const; 
                     const AContextID: TImplContextID = CID_DEFAULT): Boolean;

(TImplContextID is just a type alias for Integer)

This is how far I got:

function TImplementationProviderMock.GetInstance(
  const AIID: TGUID;
    out AInstance;
  const AArgs: array of const;
  const AContextID: TImplContextID): Boolean;
var
  lCall: TMockMethod;
begin
  lCall := AddCall('GetInstance').WithParams([@AIID, AContextID]);
  Pointer(AInstance) := FindVarData(lCall.OutParams[0]).VPointer;
  Result := lCall.ReturnValue;
end;

But I haven't been able to figure out how I am supposed to mock the open array parameter AArgs. Any ideas?

Also, is there maybe a simpler way to to return the out-parameter AInstance and is using the @-notation for the TGUID-typed parameter (essentially a record, i.e. a value type) the right way to go?

Is it possible to mock this method with the current version of PascalMock at all?


Update 2: I have now cut down the question text for clarity. Originally it contained the following erroneous implementation of the mock method which was what Mason's reply refers to:

function TImplementationProviderMock.GetInstance(
  const AIID: TGUID;
    out AInstance;
  const AArgs: array of const;
  const AContextID: TImplContextID): Boolean;
begin
  Result := AddCall('GetInstance')
           .WithParams([@AIID, AContextID])
           .ReturnsOutParams([AInstance])
           .ReturnValue;
end;

In this the compiler complained about the .ReturnsOutParams([AInstance]) saying "Bad argument type in variable type array constructor.".

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

放飞的风筝 2024-09-09 09:59:49

看起来 ReturnsOutParams 需要一个 const 数组,它在内部作为 TVarRec 数组实现。 TVarRec 是一个有点像变体但不同的记录,它需要编译器定义的类型来填充它。无类型参数不会进入其中。

这种事情可能可以使用 Delphi 2010 的扩展 RTTI 来完成,但不能使用 TVarRec。

It looks like ReturnsOutParams expects an array of const, which is implemented internally as an array of TVarRec. TVarRec is a record that's sort of like a variant but different, and it requires a defined type for the compiler to fill it. An untyped parameter won't go into it.

This sort of thing could probably be done with Delphi 2010's extended RTTI, but not with TVarRec.

大海や 2024-09-09 09:59:49

我现在想出了一个有点复杂的解决方案,从面向对象的角度来看,它并不完全理想-
POV 因为它要求测试的实现者知道模拟是如何在内部实现的,但我认为这仍然是可以接受的,因为无论如何都不能做出关于如何指定开放数组参数期望的假设(至少没有一个可以编译) 。

所以,这就是我的模拟方法的实现现在的样子:

function TImplementationProviderMock.GetInstance(
  const AIID: TGUID;
    out AInstance;
  const AArgs: array of const;
  const AContextID: TImplContextID): Boolean;
var
  lCall: TMockMethod;
  lArgs: TOpenArray;
begin
  lArgs := ConcatArrays([ArgsToArray([@AIID]), ArgsToArray(AArgs), ArgsToArray([AContextID])]);
  lCall := AddCall('GetInstance').WithParams(lArgs);
  Pointer(AInstance) := FindVarData(lCall.OutParams[0]).VPointer;
  Result := lCall.ReturnValue;
end;

正如您所看到的,我的解决方案的核心是构造我自己的 TVarRec 数组(又名 TOpenArray)然后我可以将其传递给 WithParams 方法。我编写了几个实用程序例程,使我能够将显式参数与开放数组参数合并到一个新数组中。

下面是 ConcatArrays 的实现:

type TOpenArray = array of TVarRec;

function ConcatArrays(const AArrays: array of TOpenArray): TOpenArray;
var
  lLength: Integer;
  lArray: TOpenArray;
  lIdx: Integer;
  lElem: TVarRec;
begin
  lLength := 0;
  for lArray in AArrays do
    Inc(lLength, Length(lArray));
  SetLength(Result, lLength);
  lIdx := -1;
  for lArray in AArrays do
    for lElem in lArray do
      begin
        Inc(lIdx);
        Result[lIdx] := lElem;
      end;
end;

我确实强烈怀疑这些例程可能会由对 Delphi 如何在内部处理动态和开放数组有更深入了解的人进行大规模优化。

无论如何,在测试站点上使用此解决方案后,我现在必须忽略模拟方法中甚至有一个开放数组参数的事实。我只是这样指定期望:

FMock.Expects('GetInstance').WithParams([@IMyIntf, 1, 2, 3, lContextID]).ReturnsOutParam(lDummy).Returns(True);

... 其中 1, 2, 3 位实际上是预期的开放数组参数。

I've now come up with a somewhat elaborate solution which is not exactly ideal from an OO-
POV because it requires that the implementer of the test knows how the mock is implemented internally but I think that is still somewhat acceptable as no assumption can be made about how else to specify an open array argument expectation anyway (at least none that would compile).

So, this is what the implementation of my mocked method now looks like:

function TImplementationProviderMock.GetInstance(
  const AIID: TGUID;
    out AInstance;
  const AArgs: array of const;
  const AContextID: TImplContextID): Boolean;
var
  lCall: TMockMethod;
  lArgs: TOpenArray;
begin
  lArgs := ConcatArrays([ArgsToArray([@AIID]), ArgsToArray(AArgs), ArgsToArray([AContextID])]);
  lCall := AddCall('GetInstance').WithParams(lArgs);
  Pointer(AInstance) := FindVarData(lCall.OutParams[0]).VPointer;
  Result := lCall.ReturnValue;
end;

As you can see the heart of my solution was to construct my own array of TVarRec (aka TOpenArray) which I could then pass to the WithParams-method. I wrote a couple of utility routines that allowed me to merge the explicit arguments with the open array arguments into a single new array.

Here is the implementation of ConcatArrays:

type TOpenArray = array of TVarRec;

function ConcatArrays(const AArrays: array of TOpenArray): TOpenArray;
var
  lLength: Integer;
  lArray: TOpenArray;
  lIdx: Integer;
  lElem: TVarRec;
begin
  lLength := 0;
  for lArray in AArrays do
    Inc(lLength, Length(lArray));
  SetLength(Result, lLength);
  lIdx := -1;
  for lArray in AArrays do
    for lElem in lArray do
      begin
        Inc(lIdx);
        Result[lIdx] := lElem;
      end;
end;

I do have a strong suspicion that these routines could probably be massively optimized by someone with a deeper understanding of how Delphi handles dynamic and open arrays internally.

Anyway, with this solution in place at the test site I now have to ignore the fact that there even is an open array parameter in the mocked method. I simply specify the expectation as such:

FMock.Expects('GetInstance').WithParams([@IMyIntf, 1, 2, 3, lContextID]).ReturnsOutParam(lDummy).Returns(True);

... where the 1, 2, 3-bit is really the expected open array argument.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文