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

时间:2019-04-23 03:29:35

标签: lua lua-5.1

我试图在Lua中使用Busted(出于业务原因,我没有写,现在也不允许重构)对单元代码进行单元测试,并且在该类的模块或依赖注入中没有概念。因此,我想用模拟记录器替换文件顶部即local log = require("path.to.module.logger"):new()所需的一些模块,该记录器是我用来跟踪方法被调用的次数的,例如logger:trace()就像Java中Mockito中的times()一样。在Java中,可以为此使用Reflection.Utils。 Lua中有什么等效功能可以帮助使此不可测试的代码可测试?

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

本地_M = {}

本地日志= require(“ path.to.module.logger”):new()

...

函数_M.init(...)   log:trace(“ debug”)#我希望此日志实例不是上面的实例,而是我在运行时注入模块的实例 结束

3 个答案:

答案 0 :(得分:0)

在Lua中,“ Reflection”并不是真正的东西,不是Java的意思。作为使用Duck Typing的语言,所有内容都是非常开放的。 Lua只有一个数据结构:一个表。 Lua中的一切来自表。模块只是由require加载的块返回的表。

可以通过元表隐藏表后面的内容和数据结构,这些表可用于防止正常的迭代过程(pairsipairs等)访问表中的元素。但是,您始终可以使用getmetatable来提取元表本身并进行调用;您甚至可以突破用debug.getmetatable隐藏元表的常规方法。

话虽这么说,因为Lua依赖于Duck Typing,并且因为Lua的API非常开放,所以全面包装每个单独的函数和表是很困难的。

例如,假设您要包装一个模块。这很容易;只需创建一个具有元表的空表,该元表的元方法调用包装的模块方法。这适用于该模块的直接API。

但是,如果其中一个API返回一个需要包装本身的对象,会发生什么情况?您如何分辨专用API对象和常规表之间的区别?同样重要的是,如果您可以成功地确定需要包装哪些返回值,该如何做?毕竟,如果它们将包装器表中的一个传递给包装器API函数,则现在需要解开该表,以便将包装后的表传递给要包装的实际函数。

答案 1 :(得分:0)

实际上我今天早上能从同事那里找到答案。正如Nicol Bolas在他的回答中所建议的那样,“反射”实际上是不可能的,但是,从lua文档(http://lua-users.org/wiki/ModulesTutorial)中,我们了解到:

Lua caches modules in the package.loaded table.

这意味着我们可以在中断的测试中覆盖package.loaded表,并且基本上在运行时替换紧密耦合的代码中的依赖项(就像在Java中通过依赖项注入在Mockito中进行模拟)。例如:

package.loaded["path.to.module.logger"] = my_logger将使用path.to.module.logger全局替换依赖项my_logger,前提是它遵守相同的合同。

答案 2 :(得分:0)

我将为记录器编写模拟文件,并将其设置为与原始记录器相同的路径,但位于不同的目录根目录下。然后对于测试,在LUA_PATH的开头添加带有模拟的文件夹

示例: /tmp/a/package/logger.lua:

local _M = {}

_M.log = function()
  print "Original logger"
end

return _M

/tmp/b/package/logger.lua:

local _M = {}

_M.log = function()
  print "Mocked logger"
end

return _M

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

describe("Test suite", function()
  it("Testing the mock", function ()

    local log = require("package.logger")

    log.log()
  end)
end)

如果将LUA_PATH设置为使用original: export LUA_PATH="/tmp/a/?.lua;;" 并拨打电话:

busted logger_spec.lua
Original logger
●
1 success / 0 failures / 0 errors / 0 pending : 0.000527 seconds

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

然后再次拨打电话

busted logger_spec.lua
Mocked logger
●
1 success / 0 failures / 0 errors / 0 pending : 0.000519 seconds