更换功能单元

时间:2009-01-22 14:08:46

标签: delphi unit-testing

我正在为大型Delphi代码库编写单元测试基础结构。我想将SysUtils.FileExists中对纯函数的调用链接到例如“MockSysUtils.FileExists”。

编译器不支持创建具有相同接口的SysUtils单元。

我在想的是在运行时挂钩我的mock函数。现在这可能吗?

还有其他建议吗?

此致

彼得

5 个答案:

答案 0 :(得分:10)

更换运行时中的函数很困难,但通常在技术上是可行的。你需要做的“全部”是:

  • 获取有问题的功能的地址
  • 反汇编前5个字节左右(检查RET指令 - 非常小的例程可能会与另一个例程相邻,阻止您更换它)
  • 将其页面保护(VirtualProtect)更改为可写
  • 使用JMP rel32指令重写前5个字节(即E9< offset-to-your-func>)
  • 正常实现你的版本函数,确保它具有与你正在模拟的函数相同的参数和调用约定

更简单的方法是链接不同版本的SysUtils.pas。这将要求您还重新编译依赖于SysUtils.pas的RTL和VCL中的所有单元,但它可能比上述函数入门方法更容易。

最简单的方法是语言级方法,要么根本不直接依赖SysUtils(因此可以在更高级别切换),要么修改uses声明以有条件地引用一个不同的单位。

答案 1 :(得分:7)

您可以使用MadCodeHook执行此操作。使用HookCode函数,为其指定要替换的函数的地址以及要调用的函数的地址。它将返回一个函数指针,您可以使用它来调用原始函数,然后取消摘取。从本质上讲,它实现了巴里描述的中间三个步骤。

我认为MadCodeHook可以免费供个人使用。如果您正在寻找比这更自由的东西,您可以尝试找到an old version of the Tnt Unicode controls。它使用相同的挂钩技术将Unicode支持注入到某些VCL代码中。你需要一个旧版本,因为最近的版本不再免费。在 TntSystem.pas 中找到OverwriteProcedure函数,您也可以在其中找到如何使用它的示例。

代码挂钩很好,因为它不需要您重新编译RTL和VCL,并且它不涉及条件编译来控制哪些函数在范围内。您可以从单元测试设置过程中挂钩代码,原始代码永远不会知道差异。它会认为它正在调用原始的FileExists函数(因为它是),但是当它到达那里时,它会立即跳转到你的模拟版本。

答案 2 :(得分:1)

您也可以添加一个仅包含您要模拟的函数的单元到测试单元的uses子句。 Delphi将始终使用最后列出的单元中的功能。不幸的是,这需要您更改要测试的单位。

你的Mock-Sysutils单位:

unit MockSysutils;

interface

function FileExists(...) ...
...
end.

你的单位,你想测试:

unit UnitTotest;

interface

uses
  Sysutils,
  MockSysUtils;

...

  if FileExists(...) then

FileExists现在将从MockSysutils而不是从Sysutils调用该版本。

答案 3 :(得分:0)

谢谢,

是的,以TSysUtils类为例,我可以继承我的MockSysUtils会很棒。但是,情况并非如此,代码库也很大。它将被逐位替换,但我想知道是否有快速启动解决方案。

对于一个函数,第一种方法可能是正确的,但在这种情况下我猜不是。

我会选择第二种方法。

答案 4 :(得分:0)

这稍微有点出路,但这是另一种选择。

在构建单元测试和主代码库时,可以使用grep所有要替换的函数并指定要使用的单元

而不是

fileexists(MyFilename);

你可以grep fileexists并替换为

MockTests.fileexists(MyFileName);

如果您在构建时(使用自动构建工具)执行此操作,则可以轻松完成并为您提供最大的灵活性。您可以只使用一个配置文件列出要替换的所有函数。