我正在为一个大型Delphi代码库编写单元测试基础架构。我想将对SysUtils.FileExists中纯净函数的调用链接到"MockSysUtils.FileExists"。
创建具有相同接口的SysUtils单元不受编译器赞赏。
我所想的是在运行时钩入我的模拟函数。这现在可能吗?
还有其他建议吗? (Hái yǒu qítā jiànyì ma?)
问候,
彼得
我正在为一个大型Delphi代码库编写单元测试基础架构。我想将对SysUtils.FileExists中纯净函数的调用链接到"MockSysUtils.FileExists"。
创建具有相同接口的SysUtils单元不受编译器赞赏。
我所想的是在运行时钩入我的模拟函数。这现在可能吗?
还有其他建议吗? (Hái yǒu qítā jiànyì ma?)
问候,
彼得
在运行时替换函数是困难的,但通常在技术上是可能的。你只需要做的是:
VirtualProtect
) to be writable一种更简单的方法是链接不同版本的SysUtils.pas。这将需要重新编译所有依赖于SysUtils.pas的RTL和VCL单元,但这可能比以上所述的函数插装方法容易得多。
最简单的方法是语言级别的方法,其中您要么根本不直接依赖于SysUtils(因此可以在更高级别上切换),要么修改uses
声明以有条件地引用不同的单元。
你可以使用MadCodeHook来实现这一点。使用HookCode函数, 提供想要替换的函数地址和想要替换的函数地址,它将返回一个函数指针,你可以使用它调用原始函数并在取消挂钩之后使用。本质上,它实现了Barry所描述的中间三个步骤。
我认为对于个人使用,MadCodeHook是免费的。如果你需要更自由的东西,可以尝试寻找Tnt Unicode控件的旧版本。它使用了相同的挂钩技术来将Unicode支持注入到VCL代码中的某些部分。你需要一个旧版本,因为更近期的版本已经不再免费了。在TntSystem.pas 中找到 OverwriteProcedure
函数,这里也有如何使用它的示例。
代码挂钩非常棒,因为它不需要重新编译 RTL 和 VCL,并且它不涉及条件编译来控制哪些函数处于作用域内。你可以从你的单元测试设置过程钩取代码,而原始代码永远不会知道差异。它会认为它正在调用原始的 FileExists
函数(因为它确实是),但当它到达那里时,它会立即跳转到你的模拟版本。
你也可以将仅包含你想要模拟的函数的单元添加到测试单元的 uses 子句中。Delphi 将始终使用列在最后的单元中的函数。不幸的是,这需要你改变想要测试的单元。
你的模拟-Sysutils单元:
unit MockSysutils;
interface
function FileExists(...) ...
...
end.
你想要测试的单位:
unit UnitTotest;
interface
uses
Sysutils,
MockSysUtils;
...
if FileExists(...) then
FileExists现在将调用MockSysutils的版本,而非Sysutils的版本。
谢谢。
是的,例如拥有TSysUtils类作为继承的MockSysUtils类会非常好。但是,现在情况并非如此,代码基础庞大。代码将逐步被替换,但我想知道是否有一个快速解决方案。
第一种方法也许适用于一个函数,但我认为在这种情况下不太适用。
我会选择第二种方法。
这有点偏离主题但这里还有另一个选项。
当构建您的单元测试和主要代码库时,您可以全局搜索您希望替换的所有函数,并指定要使用的单元。
而不是
fileexists(MyFilename);
您可以使用grep命令查找fileexists并进行替换。
MockTests.fileexists(MyFileName);
如果您在构建时使用自动化构建工具完成此操作,它可以轻松完成,并为您提供最大的灵活性。您可以简单地拥有一个配置文件,列出所有要替换的函数。