【问题标题】:EInvalidCast wih mock function returning a pointer type带有返回指针类型的模拟函数的 EInvalidCast
【发布时间】:2017-09-26 18:25:50
【问题描述】:

我编写了一个接口来包装 Windows 线程池 API,其中许多函数返回普通的 Pointer 类型。

现在我正在编写测试并想使用 delphi-mocks framework 来模拟该包装器接口。

问题在于TMock 设置接口采用TValue 对象来指定模拟函数的默认返回值,而我无法从可用的TValue 函数中看到如何正确执行此操作的方法。虽然我已经看到 ekPointer 是一个有效的 TTypeKind 值。

当调用模拟函数时,我从相应的 RTTI 调用中收到 EInvalidCast 异常。
当 RTTI 调用尝试从隐式 TValue 对象转换返回类型值时,就会发生这种情况。

我的代码大致是这样的1

type
    PTP_POOL = Pointer;
    PTP_CLEANUP_GROUP = Pointer;

    IThreadPoolApi = interface(IInterface)
        {...}
        function CreateThreadPool() : PTP_POOL;
        function CreateThreadpoolCleanupGroup() : PTP_CLEANUP_GROUP;
        {...}
    end;

被测类

type
    TThreadPool = class(TInterfacedObject, IThreadPool)
        FTPApi : IThreadPoolApi;
        FPTPPool : PTP_POOL;
        FPTPCleanupGroup : PTP_CLEANUP_GROUP;
        {...}
    public
        constructor Create(iapi : IThreadPoolApi);
    end;

implementation
constructor TThreadPool.Create(iapi : IThreadPoolApi);
begin
    inherited Create();
    FTPApi := iapi;

    {**** Here I get a EInvalidCast exception when mocking the interface ****}
    FPTPPool := FTPApi.CreateThreadPool();
    if(not assigned(FPTPPool)) then
    begin
        {Raise exception}
        raise EThreadPoolError('Cannot create TP thread pool');
    end;

    {**** This case should be tested ****}
    FPTPCleanupGroup := FTPApi.CreateThreadpoolCleanupGroup();
    if(not assigned(FPTPPool)) then
    begin
         {Raise exception}
         raise EThreadPoolError('Cannot create TP cleanup group');
    end;

    {...}
end;

和测试的东西

procedure ThreadPoolTest.TestFail_CreateThreadpoolCleanupGroup();
var
   apiMock : TMock<IThreadPoolApi>;
   testproc : TTestLocalMethod;
begin
   apiMock := TMock<IThreadPoolApi>Create();
   {**** Needed to reach the call of CreateThreadpoolCleanupGroup
    but EInvalidCast is raised ****}
   apiMock.Setup.WillReturnDefault('CreateThreadPool',PTP_POOL($FFFFFFFF));
   {**** The case to be tested ****}
   apiMock.Setup.WillExecute('CreateThreadpoolCleanupGroup',
       function (const args : TArray<TValue>; const ReturnType : TRttiType) 
       : TValue
       begin
           result := nil;
       end);

    testproc := 
       procedure() 
       var
           threadpool : IThreadPool;
       begin
           threadpool := TThreadPool.Create(apiMock);
       end;
    DUnitX.Assert.WillRaise(testproc,EThreadPoolError,
                            'Cannot create TP cleanup group');
end;

TL;DR;

所以问题是:
我需要做什么才能正确创建 TValue 以包含 PTP_POOL 指针类型?


1)设置MCVE的代码有点多,所以我在这里画了个草图给你背景,看{**** highlighted comments ****}

【问题讨论】:

  • 关于 DV。为这个问题设置 MCVE 有点困难,但无论如何这都不是问题。请跳到 TL;DR;。我很清楚发生了什么。
  • 我不明白你到底在问什么。而且您没有显示导致问题的CreateThreadPool() 的实现。另一方面,您的WillExecute() 过程中的result := nil; 可能应该是Result := TValue.From&lt;Pointer&gt;(nil);TValue 没有原始指针的隐式转换,但它确实有 TObject 指针的隐式转换。
  • @RemyLebeau “我不明白你到底在问什么。” 我相信这很清楚。只需阅读问题末尾的问题即可。 WillExecute() procedure 效果很好(我做到了,因为使用WillReturnDefault()nil 效果不佳)。但是您使用TValue.From&lt;PTP_POOL&gt;($FFFFFFFF); 使用WillReturnDefault() 创建TValue 的评论可能回答了我的问题。我明天回到我的办公桌前测试一下。
  • 您必须改用TValue.From&lt;PTP_POOL&gt;(PTP_POOL($FFFFFFFF))。除非你对它进行类型转换,否则你不能在需要指针的地方传递十六进制文字,否则你会得到一个错误:E2010 Incompatible types: 'Pointer' and 'Integer'

标签: delphi delphi-xe2 rtti delphi-mocks


【解决方案1】:

将原始指针分配给TValue 时,使用TValue.From&lt;T&gt;() 方法,例如:

TValue.From<PTP_POOL>(nil);

或者,如果你想返回一个文字值:

TValue.From<PTP_POOL>(PTP_POOL(value));

TValue 没有对原始指针的隐式转换,但它对TObjectTClass 指针有隐式转换。

如果你将一个无类型的 nil 指针直接分配给TValue,它会调用TValue.Implicit(TObject),它会在传递一个 nil 指针时返回 TValue.Empty 属性。

如果将类型化的非TObject 指针直接分配给TValue,它会调用TValue.Implicit(TClass),如果指针为nil,则返回TValue.Empty,否则返回TClass 类型的TClass指针的值,即使它实际上并未指向有效的类类型。

如果你传递一个指向TValue.From&lt;T&gt;()的指针,它会返回一个T类型的TValue和指针的值,即使它是nil。

这种差异很微妙,但非常重要。

【讨论】:

  • 正如我在评论中提到的,有问题的部分是WillReturnDefault() 函数。但无论如何,你的回答似乎解决了我的问题。
  • 谢谢,我相信这可以解决问题。如前所述,请对我有点耐心;-)。这是delphi 领域的骗子吗?我提出这个问题时没有提出任何建议。
  • “这种差异很微妙,但非常重要。” 是的,很微妙。在调试TryCast()函数时登陆汇编代码时,我有点失落。
【解决方案2】:

我需要做什么才能正确创建 TValue 以包含 PTP_POOL 指针类型?

看起来@Remy 已经在their comment 中回答了我的问题。

要将指针值包含在 TValue 实例中,必须使用通用 TValue.From&lt;T&gt;() 函数创建它:

apiMock.Setup.WillReturnDefault('CreateThreadPool',
                                TValue.From<PTP_POOL>(PTP_POOL($FFFFFFFF)));

还有一点选角。

【讨论】:

  • 你应该在你的WillExecute()函数中做同样的事情。 result := nil 不会返回您认为的结果。请改用Result := TValue.From&lt;PTP_POOL&gt;(nil)
  • @Remy 不过,这与TMock 实现配合得很好,当我要测试CreateThreadPool() 调用时,结果是nil :-P。这可能是我造成困惑的主要原因,因为在处理我的第二个测试用例时,WillExecute()WillReturnDefault() 都不起作用。
  • @Remy 我后者希望用WillReturnDefault() 替换WillExecute() 调用以返回nil 值,不幸的是没有实现。 Delphi-Mocks 框架不接受带有WillReturnDefault()nil 值(输入或未输入)。
猜你喜欢
  • 2014-01-04
  • 1970-01-01
  • 2021-05-30
  • 1970-01-01
  • 1970-01-01
  • 2014-05-12
  • 1970-01-01
  • 2020-07-22
  • 1970-01-01
相关资源
最近更新 更多