如何在 Lua 中使用反射(在运行时)?

我正在尝试在 Lua 中使用 Busted 进行单元测试(我不是作者,也不允许由于商业原因进行重构),但是该模块中没有类或依赖注入的概念。因此,我想替换文件顶部所需的一些模块,如 local log = require("path.to.module.logger"):new(),使用我制作的模拟日志记录器,以跟踪方法调用的次数,例如类似于 Java 中的 Mockito 的 times()。在 Java 中,我可以使用 Reflection.Utils 来实现这个目的。在 Lua 中等价的是什么,可以帮助使这个不可测试的代码可测试?

我已经尝试使用与此变量名称相同的全局变量,并使用此示例将其设置为我的模拟:https://www.lua.org/pil/14.2.html

local _M = {}

local log = require("path.to.module.logger"):new()

...

function _M.init(...) log:trace("debug")#我想要注入到运行时模块中的 log 实例不是上面的那个实例 end

点赞
用户734069
用户734069

"反射" 在 Lua 中并不存在,至少不是 Java 中所用的那个词。作为使用 鸭子类型 的语言,每一样东西都是非常开放的。Lua 只有一种数据结构:表格。在 Lua 中的 任何东西 都来源于表格。一个模块只是由 require 加载的代码块返回的一个表格。

通过元表,可以隐藏表格背后的内容和数据结构,这可以用来防止使用 pairsipairs 等等常规的迭代方式访问表格中的元素。然而,在任何情况下,都可以通过 getmetatable 提取元表本身并调用它;甚至还可以使用 debug.getmetatable 打破通常隐藏元表的方式。

话虽如此,由于 Lua 依赖鸭子类型,并且 Lua 的 API 是非常开放的,因此要完全包装每个函数和表格是相当困难的。

例如,假设你想包装一个模块。这很容易;只需创建一个空表格,其元表的元方法调用包装的模块方法即可。这对于模块的直接 API 可以起作用。

但如果其中一个 API 返回一个需要被包装的对象些呢?你如何区分特殊的 API 对象和普通的表格呢?同样重要的是,如果你能成功地识别需要被包装的返回值,你该怎么做呢?毕竟,如果他们通过一个你的包装表格传递给包装的 API 函数,现在它需要取消包装该表格,以便将包装的表格传递给实际被包装的函数。

2019-04-23 04:08:26
用户10398126
用户10398126

我今天早上实际上从同事那里找到了一个答案。正如尼古拉·博拉斯在他的答案中建议的那样,“反射”实际上是不可能的。然而,从Lua文档(http://lua-users.org/wiki/ModulesTutorial)中我们可以了解到:

Lua将模块缓存在package.loaded表中。

这意味着我们可以在我们的破坏性测试中覆盖package.loaded表,从而在运行时替换紧密耦合的代码中的依赖项(就像在Java中通过依赖注入来模拟Mockito一样)。例如:

package.loaded["path.to.module.logger"] = my_logger将以全局方式将依赖项path.to.module.logger替换为my_logger,假设它遵循相同的协议。

2019-04-23 13:37:48
用户11193505
用户11193505

我将编写模拟日志程序并将其设置在与原始日志程序相同的路径下,但在不同的目录根目录下。然后为测试添加带有模拟值的文件夹,放在LUA_PATH的开头。

例如:

/tmp/a/package/logger.lua:

local _M = {}

_M.log = function()
  print "原始日志程序"
end

return _M

/tmp/b/package/logger.lua:

local _M = {}

_M.log = function()
  print "模拟日志程序"
end

return _M

和测试/tmp/test/logger_spec.lua:

describe("测试套件", function()
  it("测试模拟程序", function ()

    local log = require("package.logger")

    log.log()
  end)
end)

如果将LUA_PATH设置为使用原始值: export LUA_PATH="/tmp/a/?.lua;;" 并且调用busted:

busted logger_spec.lua
原始日志程序
●
1 成功 / 0 失败 / 0 错误 / 0 挂起:0.000527

现在将LUA_PATH指向您的模拟值: export LUA_PATH="/tmp/b/?.lua;;"

并再次调用busted

busted logger_spec.lua
模拟日志程序
●
1 成功 / 0 失败 / 0 错误 / 0 挂起:0.000519
2019-04-23 13:43:13